Mockito实用技巧2
Mockito实用技巧2
1. 重置mock对象的状态
有时候我们需要重置 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);
}
5. 调用mock对象的真实方法
有些情况下,可能要调用 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