一、前言
在这次项目中,我参考别代码时,发现他们没有使用Junit写测试类,而是使用mock。我之前只是有所见识,并没有实际使用过。我也打算在我的这次功能代码测试时使用mock写测试类。故有此篇文章。
二、参考文档
- Java中使用Mock测试
- 原!!关于java 单元测试Junit4和Mock的一些总结
- 谈谈单元测试之(三):测试工具 JUnit 4
- spring boot test中mockito的运用
- JUnit + Mockito 单元测试(二)
- Mock以及Mockito的使用
三、什么是mock?它与Junit的联系与区别?
Mock一词是指“模拟,虚拟”的意思,所谓的Mock测试就是指在测试过程中,模拟出那些不容易获取或者不容易构造出来的对象,因为这些对象不是本次测试的重点,而我们只需要关注被测试的业务代码即可。Junit是单元测试框架,可以轻松的完成关联依赖关系少或者比较简单的类的单元测试,但是对于关联到其它比较复杂的类或对运行环境有要求的类的单元测试,模拟环境或者配置环境会非常耗时,实施单元测试比较困难。而这些“mock框架”(Mockito 、jmock 、 powermock、EasyMock),可以通过mock框架模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得测试变得简单。(例如service调用dao,即service依赖dao,我们可以通过mock dao来模拟真实的dao调用,从而能达到测试service的目的。)
常用的有:Mockito 、jmock 、 powermock。接下来将一一介绍。
如果之前未曾了解,建议先看第四条: 4、原生Mockito的使用
四、Mockito的使用
因为现在使用的springboot进行开发,所以我直接以springboot中的使用为例,介绍mockito的使用。
1、spring中相关注解
@InjectMocks — injects mock or spy fields into tested object automatically.
这个注解不会把一个类变成mock或是spy,但是会把当前对象下面的Mock/Spy类注入进去,按类型注入。注意:一个无法满足需求时,可以在一个测试类中写多个InjectMocks类。
@Mock 生成的类,所有方法都不是真实的方法,而且返回值都是NULL。—> when(dao.getOrder()).thenReturn("returened by mock ");
@Spy —Creates a spy of the real object. The spy calls real methods unless they are stubbed.
生成的类,所有方法都是真实方法,返回值都是和真实方法一样的。—> doReturn(“twotwo”).when(ps).getPriceTwo();
注意:
Mockito可以完成对一般对象方法的模拟,但是对于静态函数、构造函数、私有函数等还是无能为力.
2、原生Mockito的使用
① 验证方法调用(verify)
/**测试该方法被调用的几次
*注意事项:
*1>传给 Mockito.verify() 的参数必须是一个mock对象
*2>Mockito.mock() 的作用:而是根据传进去的一个类,mock出属于这个类的一个对象,
* 并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变!
*3>mock出来的对象需要手动注入:mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面
*/
Mockito.verify(mockUserManager,Mockito.times(1)).performLogin("sxl","111111");
具体使用案例:
//Controller层
public class LoginPresenter {
private UserManager mUserManager = new UserManager();
public boolean login(String username, String password) {
//调用方法
return mUserManager.performLogin(username, password);
}
public void setmUserManager(UserManager mUserManager) {
this.mUserManager = mUserManager;
}
}
//业务层
public class UserManager {
public boolean performLogin(String name,String password){
//很复杂的业务逻辑
if (username == null || username.length() == 0) return false;
if (username == null || username.length() == 0) return false;
if(name.equals("sxl") && password.equals("111111")){
return true;
}else {
return false;
}
}
}
测试结果:
@Test
public void test1() {
UserManager mockUserManager = Mockito.mock(UserManager.class);
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.setmUserManager(mockUserManager);
loginPresenter.login("sxl", "111111");
//验证UserManager的performLogin方法是否得到了调用,而且入参是否是这个值
Mockito.verify(mockUserManager).performLogin("sxl", "111111");
// 测试该方法被调用的几次
Mockito.verify(mockUserManager, Mockito.times(1)).performLogin("sxl", "111111");
}
假设实际调用时入参为: loginPresenter.login("sxl", "222222");
则会报如下错误:
② 模拟mock对象的某些方法的行为
比如模拟方法的返回值:
//模拟方法的返回值:Mockito.when(mockObject.targetMethod(args)).thenReturn(desiredReturnValue)
Mockito.when(mockValidator.validatorPassword("12345")).thenReturn(true);
比如指定一个方法执行特定的动作(一般是用在目标的方法是void类型的时候)
doAnswer
③ spy的使用
如果不mock指定返回值,仅仅mock对象之后调方法的话,个mock对象的所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等;而void方法将什么都不做。二spy的作用正好就是:除非指定,否者调用这个对象的默认实现,同时又能拥有验证方法调用的功能。
spy具体使用案例:
@Test
public void test3() {
UserManager spyUserManager = Mockito.spy(UserManager.class);
assertEquals("测试spy:", true, spyUserManager.performLogin("sxl", "111111"));
}
tips:spy和mock的区别:就是默认行为不一样:spy对象的方法默认调用真实的逻辑,mock对象的方法默认什么都不做,或直接返回默认值。
④ 多级调用测试
如果想要测试的类里面又调用了其它的类,可以使用spy。因为spy出来的对象会去真实的调用方法,而mock不会去调用的。如下代码所示:
/**
* 测试多级调用
* UserManager中有调用passwordValidator的方法。
*/
@Test
public void test4(){
PasswordValidator passwordValidator = Mockito.spy(PasswordValidator.class);
UserManager userManager = Mockito.spy(UserManager.class);
userManager.setValidator(passwordValidator);
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.setmUserManager(userManager);
boolean login = loginPresenter.login("sxl", "1");
System.out.println(login);
Mockito.verify(passwordValidator,Mockito.times(1)).validatorPassword("1");
}
3、在springboot中的使用
mock注解使用
代码如下:
@InjectMocks
UserService userService = new UserServiceImpl();
@Mock
private UserDAO userDAO;
@Test
public void test1() {
when(userDAO.getUserById(Mockito.anyString())).thenReturn("hi");
String id = userService.queryUserById("123");
Assert.assertEquals("hi", id);
}
运行结果:
spy注解使用
代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestMock2ApplicationTests4 {
@Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
@InjectMocks
UserService userService = new UserServiceImpl();
@Spy
private UserDAO userDAO = new UserDAOImpl();
@Test
public void testSpy(){
// 这个一定要加
when(userDAO.getUserById("songxl")).thenReturn("1223");
String id = userService.queryUserById("songxl");
System.out.println(id);
Assert.assertEquals("1223",id);
}
注:本测试用例中的service、dao 就是springboot中很常见的service调dao
大坑
- a.在mock或者spy一个类(一般都是mock rpc服务)时,只能选择一个。如果在测试类中对A类既使用了@spy注解,又使用 了@mock注解,就会出现问题:@spy注解的失效。代码如下所示:
@InjectMocks
UserService userService = new UserServiceImpl();
@Mock
private UserDAO userDAO;
@Spy
private UserDAO userDAO2;
@Test
public void test2() {
when(userDAO2.getUserById("hi")).thenReturn("hello");
String id = userService.queryUserById("hi");
System.out.println(id);
Assert.assertEquals("hi", id);
}
- b. 如果只是对A类使用了@spy注解,没有使用when().thenReturn(),效果就是相当于真实地去调用该方法。
/**
* 此方法中没有加 when().thenReturn();
* 结果:会正常调用dao层,返回实际执行的数据
*/
@Test
public void testSpy() {
String id = userService.queryUserById("songxl");
Assert.assertEquals("songxl", id);
}
- c. 如果对A类使用了@spy注解,且使用了when().thenReturn(),效果是虽然会去调用该方法,但是不会按照thenReturn的结果返回数据。
/**
* 此方法中加 when().thenReturn();
* 结果:会正常调用dao层,但是按照thenReturn()的值返回
*/
@Test
public void test1() {
when(userDAO.getUserById("hi")).thenReturn("hello");
String id = userService.queryUserById("hi");
Assert.assertEquals("hello", id);
}
- d. 如果在测试时入参和出参不一致,就会按照正常的调用去执行,但是不会返回thenReturn中指定的值。
五、总结
本文介绍了mock测试的背景、在springboot中相关的注解以及简单使用。在springboot中多级调用异常总结等问题暂时还没有总结完,待下一章介绍。
因为LZ也是在网上查阅各位大佬的资料及自己的理解写的,在开头已经贴出原文地址。若果有错误,万望指正;如果涉及侵权,请联系本人删除。