基于mockito做单元测试
1.简介
- 配合断言使用(杜绝System.out)
- 可重复执行
- 不依赖环境
- 不会对数据产生影响
- Spring的上下文环境不是必备的
- 一般都配合mock类框架对数据库进行隔离
mock类使用场景:
要进行测试的方法存在外部依赖(DB,Redis,第三方接口),为了专注于对该方法的逻辑进行测试,就希望能隔离出来外部依赖,避免外部依赖成为单测的阻塞项,一般单测都是在service进行测试
创建mock对象的三种方法:
/**
* 创建mock对象的第一种方法
*/
@ExtendWith(MockitoExtension.class)
public class InitMockOrSpyMethod1 {
@Mock
private UserService mockUserService;
@Spy
private UserService spyUserServices;
@Test
public void test1(){
System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
// spy对象是另一种类型的mock对象
System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
}
}
/**
* 创建mock对象的第二种方法
*/
public class InitMockOrSpyMethod2 {
private UserService mockUserService;
private UserService spyUserServices;
@BeforeEach
public void init(){
mockUserService = Mockito.mock(UserService.class);
spyUserServices = Mockito.spy(UserService.class);
}
@Test
public void test1(){
System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
// spy对象是另一种类型的mock对象
System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
}
}
/**
* 创建mock对象的第三种方法
*/
public class InitMockOrSpyMethod3 {
@Mock
private UserService mockUserService;
@Spy
private UserService spyUserServices;
@BeforeEach
public void init(){
MockitoAnnotations.openMocks(this);
}
@Test
public void test1(){
System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
// spy对象是另一种类型的mock对象
System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
}
}
2.mock和spy对象
mock对象:
- 方法插桩:执行插桩逻辑
- 方法不插桩:返回mock对象的默认值
- 作用对象:类吗,接口
spy对象:
- 方法插桩:执行插桩逻辑
- 方法不插桩:调用真实方法
- 作用对象:类吗,接口
3.参数匹配
@Test
public void test1() {
UserDO userDO = new UserDO();
userDO.setId(1L);
doReturn("nothing").when(mockUserService).selectNameById(userDO.getId());
// 只有执行Mockito.doReturn才执行插桩
System.out.println(mockUserService.selectNameById(userDO.getId()));
// userDO2对象不进行插桩,返回默认值
UserDO userDO2 = new UserDO();
userDO2.setId(2L);
System.out.println(mockUserService.selectNameById(userDO2.getId()));
}
/**
* 此时我只想拦截UserDO类型的任意对象
* ArgumentMatchers.*拦截任意类型
*/
@Test
public void test2() {
doReturn("nothingName").when(mockUserService).selectNameById(ArgumentMatchers.anyLong());
UserDO userDO = new UserDO();
userDO.setId(1L);
System.out.println(mockUserService.selectNameById(userDO.getId()));
UserDO userDO2 = new UserDO();
userDO2.setId(2L);
System.out.println(mockUserService.selectNameById(userDO2.getId()));
}
方法插桩
指定调用某个方法时的行为,达到相互隔离的效果
- 返回指定值
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class StubTest {
@Mock
private List<String> mockList;
/**
* 指定返回值
*/
@Test
public void test1() {
// 方法一
doReturn("zero").when(mockList).get(0);
// 如果返回值不相等,则测试不通过
Assertions.assertEquals("zero", mockList.get(0));
//方法二
when(mockList.get(1)).thenReturn("one");
Assertions.assertEquals("one", mockList.get(1));
}
}
- void返回值方法插桩
/**
* void 返回值插桩
*/
@Test
public void test2() {
// 调用mockList.clear()什么也没做
doNothing().when(mockList).clear();
mockList.clear();
verify(mockList, times(1)).clear();
}
-
插桩的两种形式
- when(obj.someMethod.thenXXX):用于mock对象
- doXXX():用于mock/spy对象
区别在于不插桩时doXXX应用与spy对象时会调用真实方法
public void test4() { when(mockUserServiceImpl.getNumber()).thenReturn(99); System.out.println("mockUserServiceImpl.getNumber():"+mockUserServiceImpl.getNumber()); doReturn(99).when(spyUserServiceImpl).getNumber(); System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber()); }
-
抛异常
/**
* 断言异常
*/
@Test
public void test3() {
doThrow(RuntimeException.class).when(mockList).get(anyInt());
try{
mockList.get(10000);//此处get(10000)为空
Assertions.fail();
}catch (RuntimeException e){
// 断言表达式为真
Assertions.assertTrue(e instanceof RuntimeException);
}
}
- 多次插桩
/**
* 多次插桩
*/
@Test
public void test4() {
// 第一次调用返回1,第二次返回2,第三次返回3以及以后得调用都返回三
when(mockList.size()).thenReturn(1)
.thenReturn(2)
.thenReturn(3);
Assertions.assertEquals(1,mockList.size());
Assertions.assertEquals(2,mockList.size());
Assertions.assertEquals(3,mockList.size());
Assertions.assertEquals(4,mockList.size());
}
- thenAnswer
/**
* thenAnswer:实现指定的插桩逻辑
*/
@Test
public void test5() {
// 此处不管传入什么参数,都返回100*3
when(mockList.get(anyInt())).thenAnswer(new Answer<String>() {
/**
* 泛型是要返回插桩的返回值类型
* @param invocationOnMock
* @return
* @throws Throwable
*/
@Override
public String answer(InvocationOnMock invocationOnMock) throws Throwable {
Integer argument = invocationOnMock.getArgument(0, Integer.class);
return String.valueOf(argument*100);
}
});
String s = mockList.get(3);
Assertions.assertEquals("300",s);
}
- 执行真正的原始方法
/**
* 执行真正的原始方法
*/
@Test
public void test6() {
when(mockUserServiceImpl.getNumber()).thenCallRealMethod();
int number = mockUserServiceImpl.getNumber();
Assertions.assertEquals(1,number);
}
- verify的使用
/**
* 指定方法返回几次
*/
@Test
public void test3() {
mockUserService.add("lily", "123123", new ArrayList<>());
// 验证add方法被调用了1次
Mockito.verify(mockUserService, Mockito.times(1))
.add("lily", "123123", new ArrayList<>());
// 要么全部使用ArgumentMatchers,不能一半使用参数,一半使用ArgumentMatchers
Mockito.verify(mockUserService, Mockito.times(1))
.add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyList());
}
4.@InjectMocks注解
- 作用:如果@InjectMocks声明的变量需要用到mock,spy对象,mockito会自动使用当前类里的mock或者spy成员进行an类型或者名字注入
- 原理:构造器注入,setter注入,字段反射注入
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {
/**
* 被InjectMocks标注的属性,必须是实现类,因为Mockito会创建InjectMocks注解的类的实例,
* 并注入到被InjectMocks标注的属性中,默认创建的对象就是没有经过Mockito处理过的对象,因此
* 配合@Spy注解,变成可以调用默认方法的mock对象
*/
@InjectMocks
@Spy
private UserServiceImpl userService;
@Mock
private UserFeatureService userFeatureService;
@Test
public void test() {
int number = userService.getNumber();
Assertions.assertEquals(1, number);
}
}