编辑推荐: |
本文来自csdn,本文简单介绍了Mockito是什么,以及Mockito在单元测试中的使用,希望能对您有所帮助。 |
|
一、Mockito的介绍
1. 什么是mock测试?什么是mock对象?
先来看看下面这个示例:
从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks
从图中可以清晰的看出
mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。
知道什么是mock测试后,那么我们就来认识一下mock框架---Mockito
2. 什么是Mockito?
除了有一个好记的名字外,Mockito尝试用不一样的方法做mocking测试,是简单轻量级能够替代EasyMock的框架。使用简单,测试代码可读性高,丰富的文档包含在javadoc中,直接在IDE中可查看文档,实例,说明。更多信息:http://code.google.com/p/mockito/
3. stub和mock
相同点:Stub和Mock对象都是用来模拟外部依赖,使我们能控制。
不同点:而stub完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。而mock对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。在mocking框架中mock对象可以同时作为stub和mock对象使用,两者并没有严格区别。
更多信息:http://martinfowler.com/articles/mocksArentStubs.html
二、Mockito在单测中的使用
1. 使用Maven引入Mockito
在pom.xml文件中直接加入以下依赖,就可以使用Mockito进行单元测试了。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope> </dependency>
<dependency> <groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency> |
2. Mockito的重要注解
2.1 @Mock
很多情况下,测试一个功能需要依赖许多其他类的返回值,如果其他类没有好,这个类的功能就不能测试。
使用Mockito的功能,我们可以让单元测试脱离对其他类的依赖,如果我们需要其他类返回一个期望的返回值,我们可以直接mock这个返回值,让测试可以很顺利的继续下去,这样测试就能专注于单元内。
@Mock注解能让你仿造一个依赖,然后通过Mockito.when(class.funciton()).thenReturn(Object)这样的格式来模拟返回值。
根据一个例子我们来尝试一下,先新建一个springboot的project,然后建三个类:
UserDO
public class
UserDO {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
} |
UserManager
import org.springframework.stereotype.Service;
@Service
public class UserManager {
public UserDO getUserDO() {
UserDO user = new UserDO();
user.setName("real");
user.setAge(20);
return user;
}
} |
UserInfoManager
import org.springframework.beans.factory.annotation
.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;
public String getUserName() {
return userManager.getUserDO().getName();
}
public Integer getUserAge() {
return userManager.getUserDO().getAge();
}
} |
根据上面的代码我们可以看出UserDO是一个实体类,UserManager和UserInfoManager都是处理业务的service类,而其中UserInfoManager依赖了UserManager。
此时我们想要对UserInfoManager进行单元测试可以,在src/test包里写测试类,首先我们要保证依赖注入,所以我们要对测试类添加注解:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { JavaLearn04MockitoApplicationTests.class
}) |
并把JavaLearn04MockitoApplicationTests.class中的注解@SpringBootTest替换为@SpringBootApplication,让springboot真的跑起来,把上面的类都加载进来,不然会报这个错:
Error creating
bean with name 'com.mrh.java.mockito.UserInfoManagerTest':
Unsatisfied dependency expressed through field
'userInfoManager'; |
为了方便我们把应该注解在测试类上的注解,注解到一个BaseTest上,这样之后对所有类的测试类都可以继承这个基类,这样就不用麻烦的一个一个加注解了。
@RunWith(SpringRunner.class)
@SpringBootApplication
public class JavaLearn04MockitoApplicationTests
{
@Test
public void contextLoads() {
}
} |
import org.junit.runner.RunWith;
import org.springframework.boot.test.context .SpringBootTest;
import org.springframework.context.annotation
.PropertySource;
import org.springframework.test.context.junit4
.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { JavaLearn04MockitoApplicationTests
.class })
@PropertySource(value = { "classpath:application.properties"
})
public class BaseTest {
} |
到此为止,我们可以开始认真的写测试类了:
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory .annotation.Autowired;
public class UserInfoManagerTest extends BaseTest
{
@Autowired
private UserInfoManager userInfoManager;
@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge()
.equals(20));
}
} |
我们预测获取的age数值为20,不然就是错的。
而运行结果,果然为20.
但是如果我们不想过多的依赖userManager的完成度(比如userManager的某个方法还没完成,或者目前还没调通),而给userManger的方法一个伪造的预期结果,这样这个单元测试就可以独立的测试UserInfoManager的逻辑,而不会受到userManager的影响了,应该怎么办呢?
这就是@Mock方法可以大显身手的时候了。
从上面的userInfoManager代码中看出,我们返回的UserDO的age是20,我们在这段代码里使用@Mock注解注入了UserInfoManager,这样我们就可以通过上面提到过的方法来mock一个预期的返回值。
public class
UserInfoManagerTest extends BaseTest {
@Mock
private UserInfoManager userInfoManager;
@Test
public void getUserAger() {
Mockito.when(userInfoManager.getUserAge()) .thenReturn(80);
Assert.assertTrue(userInfoManager.getUserAge()
.equals(80));
}
} |
这段代码只有当getUserAge()方法返回80的时候才会通过,如果不是80,说明我们的mock
没有成功。
结果返回确实是80,通过这样的方法mock数据是可行的。(必须注意的是在跑mock方法之前
那么又不得不提到另外一种场景了,我们不是想要mock UserInfoManager的返回值,而是想要mock
UserManager的返回值,这样userInfoManager的代码还照常跑,这样才能验证UserInfoManager中代码的逻辑。
那么我们需要用到下一个很重要的注解@InjectMocks。
2.2 @InjectMocks
这个注解的作用是创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
使用这个注解可以创建一个实例,并且将你用@Mock注解的对象注入到这个实例中。
用这个注解我们就可以是使用@Mock创建mock的UserManager对象,并用@InjectMocks注解创建一个UserInfoManager的实例将UserManager对象注入其中。
必须提到的是使用这个注解,我们在单测方法最前面一定要执行以下语句:
MockitoAnnotations.initMocks(this); |
否则无法将Mock注入。
public class
UserInfoManagerTest extends BaseTest {
@Mock
private UserManager userManager;
@InjectMocks
private UserInfoManager userInfoManager;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@After
public void after() {
Mockito.reset(userManager);
}
@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()).thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
}
} |
从之前的代码我们可以看出来如果我们真的调用UserManager的getUserDO,返回的对象里的name应该是“real”,而我们这里预期mock成功后,返回的应该是我们的mock数据“mock”。
代码通过,说明mock的依赖正确注入了。
可以看到我们通过@Mock和@InjectMocks,我们可以对返回值和依赖的返回值进行mock,但是下面的代码却出了一点问题:
public class
UserInfoManagerTest extends BaseTest {
@Mock
private UserManager userManager;
@InjectMocks
private UserInfoManager userInfoManager;
// @Mock
// private UserInfoManager userInfoManager;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@After
public void after() {
Mockito.reset(userManager);
}
@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge()
.equals(20));
}
@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()).thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
}
} |
当我想在getUserAge的Test里面使用UserManager真方法,在getUserName的Test里使用mock的返回值时就遇到了问题,虽然我在getUserAge的Test里面并没有对UserManager进行mock,但是他也终究不是一个真的依赖,不能返回真的结果了,
只能返回null。运行结果如下图:
如果我们想对依赖做局部mock,想要的时候就mock它,不想要的时候就运行真的方法应该如何呢?
接下来就要介绍能让这个构想实现的下一个注解:@Spy。
2.3 @Spy
Mockito中的Mock和Spy都可用于拦截那些尚未实现或不期望被真实调用的对象和方法,并为其设置自定义行为。二者的区别在于:
1、Mock声明的对象,对函数的调用均执行mock(即虚假函数),不执行真正部分。
2、Spy声明的对象,对函数的调用均执行真正部分。
于是我们对自己的代码做一下调整:
public class
UserInfoManagerTest extends BaseTest {
@Spy
private UserManager userManager;
@InjectMocks
private UserInfoManager userInfoManager;
// @Mock
// private UserInfoManager userInfoManager;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@After
public void after() {
Mockito.reset(userManager);
}
@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge()
.equals(20));
}
@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()) .thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
}
} |
用@Spy注解后的方法,可以在想mock的使用mock,想用真实方法的时候使用真实方法。
结果代码通过了。
这个时候我新加一个类OrgManager。
import org.springframework.stereotype.Service;
@Service
public class OrgManager {
public String getOrgName() {
return "abc company";
}
} |
然后修改UserInfoManager增加一个依赖OrgManager的方法:
@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;
@Autowired
private OrgManager orgManager;
public String getUserName() {
return userManager.getUserDO().getName();
}
public Integer getUserAge() {
return userManager.getUserDO().getAge();
}
public String getOrgName() {
return orgManager.getOrgName();
}
} |
这个时候我们在测试类里对这个新方法进行测试:
原因正是因为我们使用@InjectMocks注入了UserManager,但是OrgManager并没有被注入,所以实际上进入方法的时候UserInfoManager的这个依赖是空的。
难道我们要在外面@Spy一个OrgManager然后和UserManager一起注入吗?但是我们并不想对这个类进行mock,如果在依赖多的情况下这样做非常繁琐,那我们能不能也增加@Spy注解UserInfoManager这样,当我们注入@Spy对象的时候使用@Spy对象,否则使用真对象呢?(不确定@Spy有这个功能)。
@InjectMocks
@Spy
private UserInfoManager userInfoManager; |
测试后发现@Spy对象能执行真实代码,但是并不能为对象注入依赖。
而@InjectMocks是可以和@Autowired一起使用的,我们可以两个个注解叠加使用,让@Autowired注解为对象注入真依赖。
@InjectMocks
@Autowired
private UserInfoManager userInfoManager; |
下图可见这样的方法是可以通过的。
现在还有第三种场景,如果我的依赖的对象还有依赖(这在大多数的项目中是非常常见的),根据上面的说法用@Spy的对象是不会自动注入依赖的,此时怎么处理呢?
我们可以叠加@Spy和@Autowired使用。这个就不举例说明了。
3. 常用方法
3.1 Mockito.when(classObject.function()) .thenReturn(Obj)
用来mock返回值上面已经提过了。
3.2 Mockito.doReturn(obj).when(classObject)
.function()
同样是用来模拟数据,但是和3.1 不同的是,3.1会真的调用方法,而3.2不会。
修改之前的测试类来验证一下:
public class
UserInfoManagerTest extends BaseTest {
@Spy
private UserManager userManager;
@InjectMocks
@Autowired
private UserInfoManager userInfoManager;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@After
public void after() {
Mockito.reset(userManager);
}
@Test
public void getUserName() {
System.out.println("run getUserName");
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()) .thenReturn(user);
System.out.println("start getUserName test");
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName test");
}
@Test
public void getUserName2() {
System.out.println("run getUserName2");
UserDO user = new UserDO();
user.setName("mock");
Mockito.doReturn(user).when(userManager).getUserDO();
System.out.println("start getUserName2 test");
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName2 test");
}
} |
如图可见:在进行when().thenReturn()的时候,我们调用了真的方法。但是在doReturn().when()方法的时候,我们并没有真的执行getUserDO(),而是直接创建了返回值。
3.3 Mockito.doNothing().when(classObject).function()
用于模拟无返回值的方法调用。比如我们的function实际跑是跑不通的(报错或没完成),用这个方法就可以让代码直接模拟他成功执行。
比如我们调整之前的代码:
UserManager:
@Service
public class UserManager {
public UserDO getUserDO() {
UserDO user = new UserDO();
user.setName("real");
user.setAge(20);
System.out.println("run getUserDO");
return user;
}
public void unfinishedFunciotn() throws Exception
{
throw new Exception();
}
} |
UserInfoManager:
@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;
@Autowired
private OrgManager orgManager;
public String getUserName() {
return userManager.getUserDO().getName();
}
public Integer getUserAge() {
return userManager.getUserDO().getAge();
}
public String getOrgName() {
return orgManager.getOrgName();
}
public boolean unfinishedFunciotn() throws Exception
{
userManager.unfinishedFunciotn();
return true;
}
} |
可以看出新加的方法是一定会抛出异常的,但是如果我们想让unfinishedFunciotn()返回true,应该如何mock呢?
@Test
public void unfinishedFunciotn() throws Exception
{
Mockito.doNothing().when(userManager) .unfinishedFunciotn();
Assert.assertTrue(userInfoManager.unfinishedFunciotn());
} |
如图所示,只要用这个方法mock了,就不会执行方法里面的代码,也就不会抛出异常了。
3.4 verify()
verify方法用于验证方法总共被调用了几次,且只能验证mock的方法。
@Test
public void getUserName2() {
System.out.println("run getUserName2");
UserDO user = new UserDO();
user.setName("mock");
Mockito.doReturn(user).when(userManager).getUserDO();
System.out.println("start getUserName2 test");
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Assert.assertTrue(userInfoManager.getUserName()
.equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName2 test");
} |
可以验证被注入userInfoManager的userManager里的getUserDO()方法被调用了两次。
|