Mockito简介
一、简介
1、概述
模拟是单元测试的重要组成部分,使用Mockito库可以轻松编写干净直观的Java代码单元测试。
2、关键术语
- Mock
模拟对象(空壳实例),所有方法默认返回值(0/false/null)。
@Mock
或
Mockito.mock(Foo.class)
- Stub
定义Mock对象当收到某种调用时模拟返回什么。
when(mock.bar()).thenReturn(x)
doReturn(x).when(mock).bar()
- Verify
验证某方法是否被调用、调用的几次等。
verify(mock).bar()
verify(mock, times(2)).bar()
- Spy
部分模拟,未Stub的方法真执行,Stub的模拟执行。
@Spy
spy(realObject)
3、依赖
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
二、功能
1、模拟方法
- 被模拟的类
public class MyList extends AbstractList<String> {
@Override
public String get(int index) {
return null;
}
@Override
public int size() {
return 1;
}
}
- 简单模拟
MyList listMock = mock(MyList.class);
when(listMock.add(anyString())).thenReturn(false);
boolean added = listMock.add("abc");
//验证方法被调用
verify(listMock).add(anyString());
//验证返回值是否设置的一样
assertFalse(added);
mock时也可以指定名称,方便调试。
MyList listMock = mock(MyList.class, "myMock");
- 模拟回答(Answer)
自定义的Answer类:
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class CustomAnswer implements Answer<String>{
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
return "Hello";
}
}
测试:
MyList listMock = mock(MyList.class, new CustomAnswer());
String value = listMock.get(1);
verify(listMock).get(anyInt());
assertTrue("Hello".equals(value));
- 模拟配置(MockSettings)
MockSettings settings = withSettings().defaultAnswer(RETURNS_SMART_NULLS);
MyList listMock = mock(MyList.class, settings);
String value = listMock.get(3);
verify(listMock).get(anyInt());
assertTrue("".equals(value));
以上例子中相关方法通过静态引入:
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.RETURNS_SMART_NULLS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import org.junit.jupiter.api.Test;
import org.mockito.MockSettings;
2、参数匹配
- 被模拟的类
public interface FlowerService {
public String analyze(String name);
public boolean isBigFlower(String name, int size);
}
- 指定参数时的返回值
FlowerService service = Mockito.mock(FlowerService.class);
//只有analyze方法参数为poppy时返回Flower
doReturn("Flower").when(service).analyze("poppy");
assertEquals(null, service.analyze("abc"));
assertEquals("Flower", service.analyze("poppy"));
- 指定任何参数返回固定值
FlowerService service = Mockito.mock(FlowerService.class);
//无论参数是什么都返回Flower
when(service.analyze(anyString())).thenReturn("Flower");
doReturn("Flower").when(service).analyze("poppy");
assertEquals("Flower", service.analyze("abc"));
assertEquals("Flower", service.analyze("poppy"));
- 模拟多个参数的方法
如果一个方法有多个参数,不能用ArgumentMatchers只处理部分参数;Mockito要求必须通过匹配器或精确值提供所有参数。
例如:
FlowerService service = Mockito.mock(FlowerService.class);
when(service.isBigFlower("poppy", anyInt())).thenReturn(true);
会抛异常:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at demo.ArgumentMatch.mock(ArgumentMatch.java:17)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(any(), eq("String by matcher"));
可以对第一个参数使用eq匹配器:
FlowerService service = Mockito.mock(FlowerService.class);
when(service.isBigFlower(eq("poppy"), anyInt())).thenReturn(true);
assertEquals(true, service.isBigFlower("poppy", 1));
3、抛出异常
- 被模拟的类
import java.util.Map;
public class MyDictionary {
private Map<String, String> wordMap;
public void add(String word, String meaning) {
wordMap.put(word, meaning);
}
public String getMeaning(String word) {
return wordMap.get(word);
}
}
- 非空返回值方法
调用MyDictionary的getMeaning()方法时抛出NullPointerException:
MyDictionary dictMock = Mockito.mock(MyDictionary.class);
//使用when().thenThrow()
Mockito.when(dictMock.getMeaning(ArgumentMatchers.anyString())).thenThrow(NullPointerException.class);
assertThrows(NullPointerException.class, () -> dictMock.getMeaning("word"));
- 空返回值方法
MyDictionary dictMock = Mockito.mock(MyDictionary.class);
//使用doThrow()
Mockito.doThrow(IllegalStateException.class).when(dictMock).add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString());
assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
- 使用异常对象
可以将异常类换为一个异常对象,此处以doThrow()方式为例:
MyDictionary dictMock = Mockito.mock(MyDictionary.class);
Mockito.doThrow(new IllegalStateException("Error occurred.")).when(dictMock).add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString());
assertThrows(IllegalStateException.class, () -> dictMock.add("word", "meaning"));
4、注解
启用Mockito注解:
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
- @Mock
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class AnnotationTest {
@Mock
private List<String> mockedList;
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void mockTest() {
mockedList.add("one");
Mockito.verify(mockedList).add("one");
assertEquals(0, mockedList.size());
Mockito.when(mockedList.size()).thenReturn(100);
assertEquals(100, mockedList.size());
}
}
- @Spy
使用@Spy注解来监视已有的实例:
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class AnnotationTest {
@Spy
private List<String> spiedList = new ArrayList<>();
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void mockTest() {
spiedList.add("one");
spiedList.add("two");
Mockito.verify(spiedList).add("one");
Mockito.verify(spiedList).add("two");
assertEquals(2, spiedList.size());
Mockito.doReturn(100).when(spiedList).size();
assertEquals(100, spiedList.size());
}
}
- @InjectMocks
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class AnnotationTest {
@Mock
private Map<String, String> wordMap;
@InjectMocks
private MyDictionary dictionary = new MyDictionary();
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void mockTest() {
Mockito.when(wordMap.get("word")).thenReturn("meaning");
assertEquals("meaning", dictionary.getMeaning("word"));
}
}
5、参数捕获
参数捕获器(ArgumentCaptor)用来捕获调用时传入的参数,方便进一步断言。
样例中使用到的类如下:
- Format
邮件格式类:
public enum Format {
TEXT_ONLY, HTML;
}
邮件类:
public class Email {
private String to;
private String subject;
private String body;
private Format format;
public Email(String to, String subject, String body) {
super();
this.to = to;
this.subject = subject;
this.body = body;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Format getFormat() {
return format;
}
public void setFormat(Format format) {
this.format = format;
}
}
- DeliveryPlatform
发送平台:
public interface DeliveryPlatform {
void deliver(Email email);
}
- EmailService
邮件服务:
public class EmailService {
private DeliveryPlatform platform;
public EmailService(DeliveryPlatform platform) {
this.platform = platform;
}
public void send(String to, String subject, String body, boolean html) {
Format format = Format.TEXT_ONLY;
if (html) {
format = Format.HTML;
}
Email email = new Email(to, subject, body);
email.setFormat(format);
platform.deliver(email);
}
}
场景:
检查发送的邮件格式是否为HTML格式,因此需要捕获并检查传递给platform.deliver()方法的参数email的值。
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class CaptorTest {
@Mock
private DeliveryPlatform platform;
@InjectMocks
private EmailService emailService;
@Captor
private ArgumentCaptor<Email> emailCaptor;
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void test() {
String to = "to@test.com";
String subject = "Using ArgumentCaptor";
String body = "Hello World!";
emailService.send(to, subject, body, true);
//捕获email
verify(platform).deliver(emailCaptor.capture());
//获取捕获的值
Email emailCaptorValue = emailCaptor.getValue();
assertEquals(Format.HTML, emailCaptorValue.getFormat());
}
}