Mockito是一个用于在软件测试中模拟对象的开源框架,使用Mockito很大程度简化了对具有外部依赖项的类的测试开发。
mock的对象就是接口或者类的一个虚拟的实现,他允许自己定义方法的输出。通常是模拟比如和其他系统的交互信息然后再进行测试验证。
mock的流程:
项目使用的是maven构建,需要添加下面的依赖。我项目中使用的是Junit4。
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-inlineartifactId>
<version>3.3.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
Mockito提供一下几种方式来创建出mock的对象:
下面看一个简单的例子:
新建两个类,Database和Service类,Database中提供两个方法,isAvailable和getUniqueId,Service类中依赖Database,提供一个query方法调用databse的isAvailable方法,都只是测试使用。
/**
* 提供两个方法
*
* @since 2022/1/4
*/
public class Database {
public boolean isAvailable() {
return false;
}
public int getUniqueId() {
return 22;
}
}
/**
* 依赖database
*
* @since 2022/1/4
*/
public class Service {
private final Database database;
public Service(Database database) {
this.database = database;
}
public boolean query(String query) {
return database.isAvailable();
}
@Override
public String toString() {
return "Using database with id: " + database.getUniqueId();
}
}
使用Mockito来模拟Database对象的单元测试可以按照下面的方式编写:
/**
* @since 2022/1/4
*/
public class ServiceTest {
@Mock
Database database; // 2
@InjectMocks
Service service; // 3
@Before
public void before() {
MockitoAnnotations.initMocks(this); // 1
}
@Test
public void testQuery() {
Assert.assertNotNull(database);
Mockito.when(database.isAvailable()).thenReturn(true); // 4
final boolean query = service.query("select * from database"); // 5
Assert.assertTrue(query);
}
}
上面讲过,Mockito可以自己配置在mock对象上调用方法的返回值。同时,对于没有配置返回值的方法,会返回空值:
下面说明一下几个常用的用法:
when().thenReturn() 和 when().thenThrow()
使用when(…).thenReturn(…)可以在方法调用的时候,使用预置的参数来返回想要指定的值。
也可以使用比如anyString或者anyInt这些方法来定义输入的参数,这边就是对于所有的输出都返回指定的值,注意,在mock的时候要么全部使用anyString这种模拟的参数,要么就全部使用真实的参数,不然mock会失败。
如果想定义多个返回的值,thenReturn也可以支持链式的调用,他会按照指定的顺序一个一个的返回。
具体案例:
/**
* @since 2022/1/21
*/
public class MockitoWhenTest {
@Mock
List<String> mockList;
@Mock
Comparator<Integer> comparator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
// 测试返回指定的值 指定参数
@Test
public void testReturnConfiguredValue() {
Mockito.when(mockList.get(0)).thenReturn("Hello");
Assert.assertEquals(mockList.get(0), "Hello");
}
// mock可以指定多个返回值 会按照顺序返回
@Test
public void testMoreThanOneReturnValue() {
Mockito.when(mockList.get(0)).thenReturn("Hello").thenReturn("World");
Assert.assertEquals(mockList.get(0), "Hello");
Assert.assertEquals(mockList.get(0), "World");
}
// 可以指定anyInt anyString等类型 不限定参数的输入 都返回配置的值
@Test
public void testReturnValueUseAnyParameter() {
// 测试用 不要在意功能
Mockito.when(comparator.compare(9999,9999)).thenReturn(100);
Assert.assertEquals(comparator.compare(9999,9999), 100);
Mockito.when(comparator.compare(anyInt(), anyInt())).thenReturn(222);
Assert.assertEquals(comparator.compare(9,232), 222);
}
}
使用when().thenReturn()可以用来抛出异常,使用方法很简单,具体案例:
/**
* @since 2022/1/21
*/
public class MockitoThrowTest {
@Test
public void testThrow() {
// 这边使用静态的mock方法模拟对象
Demo demo = Mockito.mock(Demo.class);
Mockito.when(demo.getAge(anyInt())).thenThrow(new IllegalArgumentException("Warning"));
IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, () -> demo.getAge(33));
Assert.assertEquals(exception.getMessage(), "Warning");
}
static class Demo {
public int getAge(int age) {
return age;
}
}
}
doReturn().when() 和doThrow().when()
这两个方法和when().thenReturn()、when().thenThrow()方法一样,都可以模拟mock对象方法的返回,后面这种应该好理解一点。但是对使用@Spy注解模拟的对象来说,doReturn这种方法是有用的。
对于真实的对象,可以通过@Spy注解来模拟。而且真实的对象的话,会调用真实的方法,如果继续用when().thenReturn()的话,会产生副作用,所以对于这种真实的对象,需要使用doReturn来打桩。emmm,这边我也是根据javadoc翻译的,具体的例子:
/**
* @since 2022/1/21
*/
public class MockitoDoReturnTest {
@Spy
List<String> spyList = new LinkedList<>();
@Mock
List<String> mockList;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void doReturnTest() {
// Mockito.when(spyList.get(0)).thenReturn("Hello");
// Assert.assertEquals(spyList.get(0), "Hello");
// 这边会抛出java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 异常
// spyList其实是一个空列表
Mockito.doReturn("Hello").when(spyList).get(0);
Assert.assertEquals(spyList.get(0), "Hello");
Mockito.doReturn("World").when(mockList).get(1);
Assert.assertEquals(mockList.get(1), "World");
}
}
使用verify()方法可以验证mock的方法是否满足指定的条件,比如可以验证传入的是否是自己指定的参数,该方法调用了几次等等。这种测试被称作行为测试,行为测试不会影响方法调用的接口,但是会检查是否使用了正确的参数调用方法。
示例:
/**
* @since 2022/1/21
*/
public class MockitoVerifyTest {
@Mock
private Dog dog;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testVerify() {
dog.setAge(22);
dog.setName("Test1");
dog.setName("Test2");
// 验证是否设置了age为22
Mockito.verify(dog).setAge(ArgumentMatchers.eq(22));
// 验证是否调用了2次setName("Test1")方法
Mockito.verify(dog, Mockito.times(1)).setName("Test1");
// 验证是否重来没有调用过指定的方法
Mockito.verify(dog, Mockito.never()).getAge();
Mockito.verify(dog, Mockito.never()).setName("Test3");
// 验证最后一次的mock方法是否是setName("Test2")
Mockito.verify(dog, Mockito.atLeast(1)).setName("Test2");
}
@Data
static class Dog {
private int age;
private String name;
}
}
之前第一个例子说明过@InjectMocks注解的用法,Mockito可以通过构造函数,setter方法或者属性根据类型来注入mock的对象,下面再提供一个示例。
代码示例:
/**
* @since 2022/1/21
*/
@Setter
public class AccountService {
// 依赖accountDao以及personDao对象
private AccountDao accountDao;
private PersonDao personDao;
public double getBalance() {
return accountDao.getBalance();
}
public String getName() {
return personDao.getName();
}
}
/**
* @since 2022/1/21
*/
public class AccountDao {
public double getBalance() {
return 0;
}
}
/**
* @since 2022/1/21
*/
public class PersonDao {
public String getName() {
return "";
}
}
/**
* @since 2022/1/21
*/
public class MockitoInjectTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Mock
private AccountDao accountDao;
@Mock
private PersonDao personDao;
@InjectMocks
private AccountService accountService;
@Test
public void testInjectMocks() {
// mock出accountDao以及personDao对象
Mockito.when(accountDao.getBalance()).thenReturn(55555.0);
Mockito.when(personDao.getName()).thenReturn("Jack");
Assert.assertEquals(accountService.getBalance(), 55555.0, 0);
Assert.assertEquals(accountService.getName(), "Jack");
}
}
上面讲到了thenReturn,doReturn都是对mock的方法直接指定了返回的值,对于一些复杂的mock的场景,比如说我们想根据指定的参数来计算出返回值,或者对参数做一个回调,可以使用Answers方法。
具体案例:
/**
* @since 2022/1/22
*/
public class MockitoAnswerTest {
@Test
public void doAnswerTest() {
final Car car = Mockito.mock(Car.class);
Mockito.doAnswer(invocation -> {
// 根据参数计算返回值
final String color = invocation.getArgument(0, String.class);
final String brand = invocation.getArgument(1, String.class);
return color + " " + brand;
}).when(car).getName("red", "BMW");
Assert.assertEquals(car.getName("red", "BMW"), "red BMW");
}
static class Car {
public String getName(String color, String brand) {
return "";
}
}
}
关于Mockito的使用就总结到这边,Mockito其实还有很多的用法,大家要有兴趣可以自己再找一些资料研究研究。
参考连接:https://www.vogella.com/tutorials/Mockito/article.html
代码地址:https://github.com/yzh19961031/blogDemo/tree/master/mockitoTest