添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

有时候我们需要重置 mock 对象的状态,比如说说在单个测试方法中,需要多次使用同一个 mock 对象的时候就会有使用场景。

重置 mock 对象可以清除之前设置的行为和交互记录,让后续的代码可以在一个干净的状态下继续测试。不过需要注意的是,通常这种时候应该在另外一个测试用例中写这种逻辑,有时候重置对象状态容易出问题。

重置状态方法也有好几种,先提供业务代码:

public interface UserService {
	// 添加用户
    boolean addUser(String username, String password, Integer age, boolean isActive);
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public boolean addUser(String username, String password, Integer age, boolean isActive) {
        final Long userId = 1062L;
        final int row = userMapper.insertUser(userId,username,password1062,age,isActive);
        return row > 0 ;

1.1 reset() 方法

reset() 方法会完全重置 mock 对象的状态。

    @Mock
    private UserMapper userMapper;
    @InjectMocks
    private UserServiceImpl userService;
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    @Test
    public void testResetMethod() {
        // 第一次测试
        when(userMapper.insertUser(anyLong(), anyString(), anyString(), anyInt(), anyBoolean())).thenReturn(1);
        userService.addUser("user1062", "password1062", 30, true);
        verify(userMapper).insertUser(eq(1062L), eq("user1062"), eq("password1062"), eq(30), eq(true));
        // 重置这个 mock对象,之前的行为跟记录就没了
        reset(userMapper);
        // 再一次次测试
        userService.addUser("user1063", "password1063", 35, false);
        verify(userMapper).insertUser(eq(1063L), eq("user1063"), eq("password1063"), eq(35), eq(false));

1.2 clearInvocations() 方法

clearInvocations() 方法只会清除 mock 对象的调用记录,也可以起到重置对象状态的作用。

    @Mock
    private UserMapper userMapper;
    @InjectMocks
    private UserServiceImpl userService;
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    @Test
    public void testClearInvocations() {
        // 第一次调用
        when(userMapper.insertUser(anyLong(), anyString(), anyString(), anyInt(), anyBoolean())).thenReturn(1);
        userService.addUser("user1062", "password1062", 30, true);
        verify(userMapper).insertUser(eq(1062L), eq("user1062"), eq("password1062"), eq(30), eq(true));
        // 清除调用记录
        clearInvocations(userMapper);
        // 再一次次测试
        userService.addUser("user1063", "password1063", 35, false);
        verify(userMapper).insertUser(eq(1063L), eq("user1063"), eq("password1063"), eq(35), eq(false));

reset跟clearInvocations两个方法的区别在于:

  • reset() 方法会完全重置 mock 对象的状态,相当于变成一个初始化的 mock 对象。
  • clearInvocations() 方法只会清除 mock 对象的调用记录,一些行为之类的不会清除,之前 mock 的行为还是保留。
  • 2. mock void方法逻辑

    mock void 方法在实际的单元测试用例编写过程中,也是一个常见的需求。 void 方法虽然是没有返回值,但有时候也需要需要模拟下行为。

    大部分情况下,使用 doAnswer() 方法是最合适的一种方式。因为在 doAnswer() 方法里面可以自定义的逻辑,刚好符合要求。

            final List<String> mockedList = mock(List.class);
            doAnswer(new Answer<Void>() {
                @Override
                public Void answer(InvocationOnMock invocation) throws Throwable {
                    final Object[] args = invocation.getArguments();
                    // 获取第一个入参
                    final int index = (int) args[0];
                    // 获取第二个入参
                    final String element = (String) args[1];
                    System.out.printf("触发 mockedList.add() 方法,下标: %d, 元素值: %s%n", index, element);
                    return null;
            }).when(mockedList).add(anyInt(), anyString());
    

    上面这个例子就是 mock 掉 List.add() 方法,调用这个方式的时候,打印出添加的元素,而不会真正的添加到 List 中。

    当跟 Java8 的 Consumer 接口结合一起使用的时候,就可以这么写:

           final List<String> mockedList = mock(List.class);
            doAnswer((Answer<Void>) invocation -> {
                final Object[] args = invocation.getArguments();
                // 获取第一个入参
                final int index = (int) args[0];
                // 获取第二个入参
                final String element = (String) args[1];
                System.out.printf("触发 mockedList.add() 方法,下标: %d, 元素值: %s%n", index, element);
                return null;
            }).when(mockedList).add(anyInt(), anyString());
            mockedList.add(0, "第一个元素");
            mockedList.add(1, "第二个元素");
            mockedList.add(0, "第三个元素");
    

    当然上面就是演示 mock void 函数的一个例子,根据实际场景可以写出更多实用的测试用例。

    3. 忽略void方法调用

    对于 void 的方法,Mockito 的默认行为就是什么都不做也不处理。

    4. 使用doThrow()对void方法抛出异常

    @Test(expected = RuntimeException.class) public void testVoidMethodThrowsException() { doThrow(new RuntimeException("删除失败")).when(userMapper).deleteUser(anyLong()); userService.removeUser(1L);

    有些情况下,可能要调用 mock 对象的真实实现,而不是使用模拟的逻辑。 这种时候可以用 thenCallRealMethod()方法指定方法调用应该调用真实的实现逻辑,不走 mock 逻辑。

    	@Test
    	public void testThenCallRealMethod() {
    	    final UserMapper userMapper = mock(UserMapper.class);
    	    when(userMapper.findUserById(anyLong())).thenCallRealMethod();
    	    // 调用 findUserById 的真实实现,这里不处理会抛异常
    	    fianl User user = userMapper.findUserById(1L);
    

    6. 将普通对象的实例变量替换为 mock 对象

    有时候一些测试的对象可能没有办法使用依赖注入,这种比较常见在一些遗留系统代码里面。这种时候可以使用反射 API 将mock对象设置到对应的变量中去。

        @Test
        public void testWithReflectionTestUtils() {
        	// 假设这个对象不能直接进行 mock 
            final UserService userService = new UserService();
            // UserMapper 对象类需要 mock
            final UserMapper mockMapper = mock(UserMapper.class);
            // 调用反射设置这个变量的值
            ReflectionTestUtils.setField(userService, "userMapper", mockMapper);
    
  • How to mock Consumer<> Lambda in java 8 https://github.com/mockito/mockito/issues/1384
  • How to mock injected dependency with a Consumer<> lambda https://groups.google.com/g/mockito/c/TsmWevThhrk
  •