记录一次使用mockito遇到的问题

325 阅读3分钟

Mockito
常用注解 @Mock @Spy @InjectMocks

记录一次在使用mockito时候遇到的问题,然后进行仔细研究一番;以下有一段代码,我们的场景是对某个mock对象执行方法内部的方法再次进行mock数据返回时候,碰到的问题,可以一起学习一下。先粘贴一部分代码:
PersonService:

@Service
public class PersonServiceImpl implements PersonService {
    @Autowired
    private BookService bookService;
    @Override
    public String myBooks() {
        String name = "java编程";
        System.out.println("我就一本书:java编程");
        String book = bookService.findBook(name);
        int i = bookNums(name);
        return i + "本" +book;
    }
    public int bookNums(String name){
        return 6;
    }
}

BookService:

public interface BookService {
    String findBook(String name);
}

上述代码我们想对上述方法进行单元测试,当我们需要对一个mock对象中调用的某个方法再次进行插桩时候可以进行实现方式有两种,例如我们想mock方法BookService#findBook返回值以及PersonServiceImpl#bookNums返回值,此时有两种方法: 如果你是新接触的,大概率会写成:

public class PersonTest {
    /**
    * 使用@InjectMocks可以将@Mock声明的bookService注入到personServiceImpl中进行使用
    */
    @InjectMocks
    PersonServiceImpl personServiceImpl;
    @Mock
    BookService bookService;
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void findBookTest1(){
        when(bookService.findBook(anyString())).thenReturn("good");
        when(personServiceImpl.bookNums(anyString())).thenReturn(5);
        String s = personServiceImpl.myBooks();
        System.out.println(s);
    }
}

此时会报错:

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

This message may appear after an NullPointerException if the last matcher is returning an object 
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
    when(mock.get(any())); // bad use, will raise NPE
    when(mock.get(anyInt())); // correct usage use

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

原因就是when(personServiceImpl.bookNums(anyString())).thenReturn(5);这行代码中personServiceImpl是真实的对象,而非mock对象。

针对于上述,我们还想对personServiceImpl中的bookNums()进行mock数据时候,有两种方式:
第一种:
使用注解@InjectMocks配合注解@Spy一起使用。使用@Spy代表创建真实对象,使用此注解后我们便可以对真实对象中的某个方法进行插桩,所以再次执行when(personServiceImpl.bookNums(anyString())).thenReturn(5);这行代码便不会报错。

@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonTest {
    /**
    * 使用@InjectMocks可以将@Mock声明的bookService注入到personServiceImpl中进行使用
    */
    @InjectMocks
    @Spy
    PersonServiceImpl personServiceImpl;
    @Mock
    BookService bookService;
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
    }
    @Test
    public void findBookTest1(){
        when(bookService.findBook(anyString())).thenReturn("good");
        when(personServiceImpl.bookNums(anyString())).thenReturn(5);
        String s = personServiceImpl.myBooks();
        System.out.println(s);
    }
}

第二种:
两个对象均声明为mock对象,手动将bookService对象注入personServiceImpl对象中,使用Mockito.doCallRealMethod()方法进行执行真实调用某个方法,所以此处的方式改成:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PersonTest {
    /**
    * 此时均声明为mock对象
    */
    @Mock
    PersonServiceImpl personServiceImpl;
    @Mock
    BookService bookService;
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
        //此处手动将bookService对象注入personServiceImpl对象中
        ReflectionTestUtils.setField(personServiceImpl,"bookService",bookService);
    }
    @Test
    public void findBookTest1(){
        when(bookService.findBook(anyString())).thenReturn("good");
        //当mock对象personServiceImpl执行到myBooks()方法时候,则进行真实调用,便可以实现想要的效果
        doCallRealMethod().when(personServiceImpl).myBooks();
        //对mock对象personServiceImpl.bookNums()进行插桩
        when(personServiceImpl.bookNums(anyString())).thenReturn(5);
        String s = personServiceImpl.myBooks();
        System.out.println(s);
    }
}

结论: 被@InjectMocks注解修饰的对象,不是mock对象,是一个真实对象。当通过MockUtil.isMock(object)判断时候返回的是false。 当通过使用Mockito.when(object).thenReturn(null)时候进行插桩时候,要求object必须是mock对象才可以继续执行,否则会报错。