一、前言
在第一篇文章中(使用mockito写测试用例(一)),介绍了使用mock写一些的测试类。但是对于一些复杂的测试类,使用mockito还是有些困难的。但是为了覆盖率,某些类的某些方法又必须测试得到,这就是个问题。
二、Mockito的特点
①、mock的测试类无法调用静态方法(使用powermock解决)
②、如果想要mock多个层级的类,就比较复杂了。
三、使用技巧
1、对私有方法mock
如果私有方法中有调用其他层的方法,又需要mock,测试通过反射调用时就需要注意一些细节。
如下代码所示:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
private String queryUserByOther(String test) {
System.out.println("1.进入service层, 入参:"+test);
String testId = userDAO.getUserById(test);
System.out.println("3.调用dao返回值:"+testId);
return testId;
}
}
//测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestMock2ApplicationTests5 {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@InjectMocks
UserServiceImpl userServiceImpl = new UserServiceImpl();
@Mock
private UserDAO userDAO = new UserDAOImpl();
@Test
public void testPrivate(){
try {
when(userDAO.getUserById("hi Micheal")).thenReturn("hello");
Class<? extends UserServiceImpl> aClass = userServiceImpl.getClass();
Method method = aClass.getDeclaredMethod("queryUserByOther", String.class);
method.setAccessible(true);
String test = (String) method.invoke(userServiceImpl , "hi Micheal");
Assert.assertEquals("测试结果:","hello",test);
} catch (Exception e) {
e.printStackTrace();
}
}
注意:这有一个坑,在method的invoke方法中,如果不使用userServiceImpl,而使用的是aClass.newInstance(),则会出现NPE。原因是:我已经将userServiceImpl这个实例设定为被测试的目标类,而通过mock的dao对象就会注入到该实例中。所有如果使用其他实例(aClass.newInstance()),肯定没有注入dao对象,当然无法调用dao的方法。
2、mock多层级的调用
在上一篇博客中知识简单介绍了mock的使用(文章开头有链接),但是在springboot中基于注解式的并没有介绍。本节将详细介绍。工作中都会遇到这么一种场景:在mock测试一个A类中,需要调用另一个B类的某些方法去执行,所以直接mock掉肯定不行。这时候就用到了spy,但是呢,B类中的这个方法又依赖于C类,但是不需要真实调用C类,所以我们需要将C类mock。代码如下:
//service层
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public String queryUserById(String id) {
System.out.println("1.进入service层, 入参:"+id);
String userById = userDAO.getUserById(id);
System.out.println("4.调用dao返回值:"+userById);
return userById;
}
}
//DAO层
@Data
@Service
public class UserDAOImpl implements UserDAO {
@Autowired
private UserDataBase userDataBase;
@Override
public String getUserById(String id) {
System.out.println("2. 进入dao 层,入参为:" + id);
String userById = userDataBase.getUserById(id);
System.out.println("3.调用database返回结果:" + id);
return userById;
}
}
//测试用例
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestMock2ApplicationTests4 {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@InjectMocks
UserService userService = new UserServiceImpl();
@Spy
private UserDAOImpl userDAO = new UserDAOImpl();
@Mock
private UserDataBase userDataBase;
@Test
public void test5(){
//关键点
userDAO.setUserDataBase(userDataBase);
when(userDataBase.getUserById(Mockito.anyString())).thenReturn("songxl");
String id = userService.queryUserById("songxl");
System.out.println(id);
}
}
测试结果:
总结
1)、个人认为关键点是:将DAO层的依赖接口(UserDataBase )mock时,通过set注入到UserDAO中。如果不写这行代码,则默认将UserDataBase 注入到userService中,所以后续执行时肯定会出现NPE。
2)、理论上可以解决多层调用的问题,但是需要mock、spy大量的数据,但是这就有一点违背初心了。毕竟使用mock写测试用例的目的就是:只需要关注被测试的业务代码即可,其他的mock掉。如果都测试,还不如直接联调简单粗暴。
doAnswer的使用
一般情况,我们使用verify、doReturn足以完成测试,但是有时候面对一些复杂的业务逻辑,就显得力不从心了。doAnswer可以用来判断执行的方法和方法的入参,并做一些处理等等,有一箭双雕的作用(doAnswer和thenAnswer作用一样的)。如下所示
方式一:
方式二:
thenThrow的使用
在写测试用例时,我们需要验证业务逻辑是否正确,即不仅测试成功的情景还要测试失败的场景。一句话:所有的场景都要覆盖到。在含有try-catch的逻辑部分,测试catch部分时就用到了thenThrow。如下代码所示:
//mock业务逻辑
@Override
public String queryUserByName(String name) {
String daoUserByName = null;
try {
System.out.println("1.service (by name)"+name);
daoUserByName = userDAO.getUserByName(name);
} catch (Exception e) {
e.printStackTrace();
}
return daoUserByName;
}
/**
* 测试用例:
* thenThrow的使用
* 参数:必须指定一个异常。
*/
@Test
public void test1() {
RuntimeException exception = new RuntimeException("test exception");
when(userDAO.getUserByName(Mockito.anyString())).thenThrow(exception);
userService.queryUserByName("");
}
运行结果:
四、参考博客
五、总结
本文介绍了mock私有方法的测试、在springboot中多层调用。但是对于静态方法的调用,mockito无法完成,可以通过powermock完成,待下一章介绍。
因为LZ也是在网上查阅各位大佬的资料及自己的理解写的,在开头已经贴出原文地址。若果有错误,万望指正;如果涉及侵权,请联系本人删除。