Mockito 使用
大约 2 分钟
Mockito 是 Java 的模拟测试框架,通过 Mockito 可以创建和配置 Mock 对象,简化外部依赖的类的测试。
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
初始化
注解 + ExtendWith
package org.example;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class Init01Test {
@Mock // @InjectMocks —— 前者不调用实际方法,后者调用实际方法
private UserService mockUserService;
@Spy
private UserService spyUserService;
@Test
void test() {
Assertions.assertTrue(Mockito.mockingDetails(mockUserService).isMock());
Assertions.assertFalse(Mockito.mockingDetails(mockUserService).isSpy()); // 💡 mock is not spy —— mock 实际为空调用,而 spy 需要监听实际方法调用,因此冲突
Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isSpy());
Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isMock()); // 💡 spy is mock —— 在 spy 监听实际方法调用的同时,提供 mock 改变返回值的功能!
}
}
<!-- 提供 MockitoExtension.class 对象 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
注释 + BeforeEach
package org.example;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class Init02Test {
@Mock
private UserService mockUserService;
@Spy
private UserService spyUserService;
@BeforeEach
void beforeEach() {
MockitoAnnotations.openMocks(this);
}
@Test
void test() {
Assertions.assertTrue(Mockito.mockingDetails(mockUserService).isMock());
Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isSpy());
}
}
手动初始化
package org.example;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
public class Init03Test {
private UserService mockUserService;
private UserService spyUserService;
@BeforeEach
public void beforeEach() {
mockUserService = Mockito.mock(UserService.class);
spyUserService = Mockito.spy(UserService.class);
}
@Test
void test() {
Assertions.assertTrue(Mockito.mockingDetails(mockUserService).isMock());
Assertions.assertTrue(Mockito.mockingDetails(spyUserService).isSpy());
}
}
提示
- Mock —— 从类型创建,不调用实际方法
- Spy —— 对现有实例的封装,调用被封装实例的实际方法
基本使用
- Mock —— 根据类名创建一个 “空对象”,可以对其返回值进行定制,若无定制返回默认值
- Spy —— 根据实际对象创建一个 “代理对象”,
- Captor —— 捕获传入的参数值
- InjectMock —— 自动注入相关引用
mock
对于一个 mock 对象,我们可以指定返回值和执行特定的动作。 当然,也可以不指定。
如果不指定返回值的话,一个 mock 对象的所有非 void 方法都将返回默认值:
- int、long 类型方法将返回 0
- boolean 方法将返回 false
- 对象方法将返回 null 等等
而 void 方法将什么都不做。
package org.example;
import org.example.entity.UserDTO;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.UnnecessaryStubbingException;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* 模拟返回结果/抛出异常
*/
@ExtendWith(MockitoExtension.class)
public class Mock01Test {
@Mock
private UserService mockUserService;
/**
* 测试 when 的 thenReturn 方法
* 💡调用多个 thenReturn 时,会按顺序返回
*/
@Test
void test_when_thenReturn() {
List<UserDTO> returnUserAll01 = List.of(new UserDTO());
List<UserDTO> returnUserAll02 = List.of(new UserDTO());
when(mockUserService.getUserAll()).thenReturn(returnUserAll01).thenReturn(returnUserAll02); // 顺序返回
Assertions.assertArrayEquals(returnUserAll01.toArray(), mockUserService.getUserAll().toArray());
Assertions.assertArrayEquals(returnUserAll02.toArray(), mockUserService.getUserAll().toArray());
Assertions.assertArrayEquals(returnUserAll02.toArray(), mockUserService.getUserAll().toArray()); // 返回最后一个 thenReturn 设定
}
/**
* 另一种写法
*/
@Test
void test_when_thenReturn_2() {
List<UserDTO> returnUserAll01 = List.of(new UserDTO());
doReturn(returnUserAll01).when(mockUserService).getUserAll();
Assertions.assertArrayEquals(returnUserAll01.toArray(), mockUserService.getUserAll().toArray());
}
/**
* ⚠️定义了 when 就要调用,否则抛异常 {@link UnnecessaryStubbingException}
* ⚠️如果确定方法 mock 后不调用,一种处理方法是使用 lenient() 提供的方法进行 mock
* 💡另一种方法是在 mock 环境建立前设置宽松的 mock 行为: Mockito.mockitoSession().initMocks(this).strictness(Strictness.STRICT_STUBS).startMocking();
*/
@Test
void test_UnnecessaryStubbingException_lenient() {
// ↓ or @Mock(lenient = true)
lenient().when(mockUserService.getUserAll()).thenReturn(new ArrayList<>()); // this won't get called
}
/**
* 动态返回
*/
@Test
void test_thenAnswer() {
UserDTO param = new UserDTO();
when(mockUserService.getUserList(any(UserDTO.class))).thenAnswer((Answer<List<UserDTO>>) invocation -> {
UserDTO argument = invocation.getArgument(0);
return List.of(argument); // 💡运行时获取传入参数(而不是 when 时就指定)
});
Assertions.assertArrayEquals(List.of(param).toArray(), mockUserService.getUserList(param).toArray());
}
/**
* 模拟异常抛出
*/
@Test
void test_doThrow() {
doThrow(ArithmeticException.class).when(mockUserService).getUserAll();
Assertions.assertThrows(RuntimeException.class, new Executable() {
@Override
public void execute() throws Throwable {
mockUserService.getUserAll();
}
});
}
}
spy
package org.example;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.exceptions.verification.WantedButNotInvoked;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class Spy01Test {
@Spy
private UserService userService;
@Test
void test_spy_is_mock() {
Assertions.assertTrue(mockingDetails(userService).isMock()); // 既是 spy 也是 mock
Assertions.assertTrue(mockingDetails(userService).isSpy());
}
/**
* 测试 verify 抛出异常的情况
*/
@Test
void test_verify() {
Assertions.assertThrows(WantedButNotInvoked.class, new Executable() {
@Override
public void execute() throws Throwable {
verify(userService).getUserAll(); // 校验发现方法未被调用(至少一次),则抛出异常
// verify(userService, times(1)).getUserAll(); // 等价 ↑
}
});
userService.getUserAll();
verify(userService).getUserAll(); // 调用后再校验,则不报错
}
/**
* 测试 spy 的实例与 spy 是否关联
*/
@Test
void test_instance_change() {
List<String> list = new ArrayList<>();
List<String> spy = Mockito.spy(list);
spy.add("1");
Assertions.assertEquals(1, spy.size());
Assertions.assertEquals(0, list.size()); // spy 与实例并不关联,相互独立
}
}
captor
package org.example;
import org.example.entity.UserDTO;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
public @ExtendWith(MockitoExtension.class)
class Captor01Test {
@Mock
private UserService userService;
@Captor
ArgumentCaptor<UserDTO> argCaptor;
@Test
void test_when_capture() {
when(userService.getUserList(argCaptor.capture())).thenReturn(null);
UserDTO userDTO = new UserDTO();
Assertions.assertNull(userService.getUserList(userDTO));
Assertions.assertEquals(userDTO, argCaptor.getValue());
}
@Test
void test_verify_capture() {
UserDTO userDTO = new UserDTO();
userService.getUserList(userDTO);
verify(userService, times(1)).getUserList(argCaptor.capture());
Assertions.assertEquals(userDTO, argCaptor.getValue());
}
}
区别 @Mock/@Spy/@Captor/@InjectMock
package org.example;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.verify;
/**
* 区分 @Mock、@Spy、@Captor、@InjectMock
*/
@ExtendWith(MockitoExtension.class)
public class DiffTypesTest {
@Mock
private List<String> mock;
@Spy
private List<String> spyNoInstance; // 💡如果没有实例化,则退化成 mock (即不调用实际方法)
@Spy
private List<String> spy = new ArrayList<>();
@Captor
private ArgumentCaptor<String> captor; // 用于获取入参
/**
* 自动注入相关引用
*/
@InjectMocks
private InjectMockClass injectMockClass = new InjectMockClass();
/**
* \@Mock 不实际调用方法
*/
@Test
void test_mock_do_nothing() {
mock.add("1");
Assertions.assertEquals(0, mock.size());
}
/**
* \@Spy 实际调用方法
*/
@Test
void test_spy_do_exactly() {
spy.add("1");
Assertions.assertEquals(1, spy.size());
spyNoInstance.add("1");
Assertions.assertEquals(0, spyNoInstance.size());
}
@Test
void test_captor() {
spy.add("1");
verify(spy).add(captor.capture());
Assertions.assertEquals(1, spy.size());
Assertions.assertEquals("1", captor.getValue());
}
/**
* \@InjectMock 注入相关 @Mock/@Spy 进入对象属性中
*/
@Test
void test_injectMock() {
// 自动注入相关属性
Assertions.assertEquals(mock, injectMockClass.mock);
Assertions.assertEquals(spy, injectMockClass.spy);
Assertions.assertFalse(mockingDetails(injectMockClass).isMock()); // 被注入属性的对象默认不是 Mock
Assertions.assertEquals(1, injectMockClass.doSomething()); // 实际执行方法
}
private static class InjectMockClass {
private List<String> mock;
private List<String> spy;
int doSomething() {
return 1;
}
}
}
verify
通过 verify 来判断方法内部实现是否符合预想。
通过对 verify(T mock)
/verify(T mock, VerificationMode mode)
的返回值的方法调用,验证某些行为是否至少发生过一次/确切的次数/从未发生过。
方法调用次数校验 | 说明 |
---|---|
times(int wantedNumberOfInvocations) | 允许验证调用的确切次数。 |
atLeast(int minNumberOfInvocations) | 允许至少 x 次调用的验证。 |
atMost(int maxNumberOfInvocations) | 允许最多 x 次调用的验证。 |
atLeastOnce() | 允许至少一次调用的验证。 |
atMostOnce() | 允许最多一次调用的验证。 |
never() | times(0)的别名,见 times(int) 。 |
only() | 允许检查给定的方法是否只调用一次。 |
类是否被调用校验 | 说明 |
---|---|
verifyNoInteractions(Object... mocks) | 验证给定的 mock 对象上没有发生交互。 |
verifyNoMoreInteractions(Object... mocks) | 检查任何给定的 mock 对象上是否有任何未经验证的交互。 |
validateMockitoUsage() | 验证测试代码中是否有书写错误的地方。如是否有漏写 return、verifyMethod 等等。 |
package org.example;
import org.example.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.exceptions.misusing.UnfinishedVerificationException;
import org.mockito.exceptions.verification.NoInteractionsWanted;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
public class VerifyTest {
@Mock
private UserService userService;
@InjectMocks
private MyTester myTester;
@Test
void test_verifyNoInteractions() {
verifyNoInteractions(userService); // 通过,因为无调用 userService
myTester.doSomething();
Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
@Override
public void execute() throws Throwable {
verifyNoInteractions(userService); // 异常,因为 myTester 内部调用 userService
}
});
verify(userService).getUserAll();
Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
@Override
public void execute() throws Throwable {
verifyNoInteractions(userService); // 异常,因为判断的是 “是否有被调用过”
}
});
}
@Test
void test_verifyNoMoreInteractions() {
verifyNoMoreInteractions(userService); // 通过,因为无调用 userService
myTester.doSomething();
Assertions.assertThrowsExactly(NoInteractionsWanted.class, new Executable() {
@Override
public void execute() throws Throwable {
verifyNoMoreInteractions(userService); // 异常,因为 myTester 内部调用 userService
}
});
verify(userService).getUserAll();
verifyNoMoreInteractions(userService); // 通过,因为判断的是 “是否没有更多异常” 了
}
@Test
void test_validateMockitoUsage() {
verify(userService);
Assertions.assertThrowsExactly(UnfinishedVerificationException.class, new Executable() {
@Override
public void execute() throws Throwable {
validateMockitoUsage(); // 异常,因为 verify 没有方法调用
}
});
}
private static class MyTester {
private UserService userService;
void doSomething() {
userService.getUserAll();
}
}
}
原理
todo 原理
@Test
public void testInit() {
Mockito.mockingDetails(mockUserService).isMock(); // true
Mockito.mockingDetails(spyUserService).isSpy(); // true
Mockito.mockingDetails(spyUserService).isMock(); // true —— ❗Spy是Mock的子类
}
问题: mock FunctionInterface 输入
todo 完善
package org.example;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.invocation.InvocationOnMock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.mockito.Mockito.*;
public class Mock02FunctionInterfaceTest {
public static class FuncHandler {
private String pid;
private boolean flag = false;
public FuncHandler(String id) {
this.pid = "p_" + id;
}
public FuncHandler method_01() {
flag = true;
return this;
}
public <T> T method_02(Function<String, T> func) {
if (!flag) {
throw new UnsupportedOperationException("please run method 01 first");
}
return func.apply(pid);
}
}
@Test
void testHandler() {
assertEquals("xx_p_" + "world", new FuncHandler("world").method_01().method_02(pid -> "xx_" + pid));
assertThrowsExactly(UnsupportedOperationException.class, () -> new FuncHandler("world").method_02(pid -> "xx_" + pid));
}
@Test
void testHandlerMock() {
try (MockedConstruction<FuncHandler> funcHandlerMockedConstruction = mockConstruction(FuncHandler.class, new MockedConstruction.MockInitializer<FuncHandler>() {
final private AtomicInteger i = new AtomicInteger(0);
private Object answer(InvocationOnMock invocation) throws Throwable {
if (i.getAndIncrement() == 0) {
Function<String, String> arg0 = invocation.getArgument(0);
return arg0.apply("p_world");
} else {
return invocation.callRealMethod();
}
}
@Override
public void prepare(FuncHandler mock, MockedConstruction.Context context) throws Throwable {
when(mock.method_01()).thenCallRealMethod();
doAnswer(this::answer).when(mock).method_02(any());
}
})) {
testHandler();
}
}
}