浅尝 Mockito
浅尝一下 Mockito
# Mockito
# 构建测试用例
@Test
public void testOrdinary() {
log.info("=======Ordinary=======");
Result ordinary = Result.success();
log.info("Ordinary: {}", Jackson.toJSONString(ordinary));
}
2
3
4
5
6
控制台输出内容:
15:12:03.462 [main] INFO c.s.c.c.MockitoTest -- =======Ordinary=======
15:12:03.584 [main] INFO c.s.c.c.MockitoTest -- Ordinary: {"state":200,"code":"SUCCESS","message":"Success","now":"2024-11-22 15:12:03.464"}
2
3
# @Mock 和 @MockBean
@Mock
是 Mockito 框架中的一个注解, 它可以帮助你创建模拟对象来替代真实的对象
@MockBean
主要用于 Spring Boot 应用的集成测试中, 它是 Spring Boot Test 框架提供的一个注解. 与@Mock
不同的是, @MockBean
不仅会创建一个模拟对象, 还会将这个模拟对象替换掉 Spring 应用上下文中对应的真实Bean.
@Test
public void testMock() {
log.info("=======Mock=======");
Result mocked = Mockito.mock(Result.class);
// 对 getNow() 打桩, 返回值: 当天 0时, 0分, 0秒
Mockito.doReturn(LocalDate.now().atTime(LocalTime.MIDNIGHT)).when(mocked).getNow();
// 在覆盖data时, 会抛出 CustomException
Mockito.doThrow(CustomException.of("Mockito.doThrow")).when(mocked).data(any());
// 指定show属性时, 返回 (Result) null 反正也不用
Mockito.doAnswer(i -> {
Boolean show = i.getArgument(0, Boolean.class);
log.info("mocked.show({})", show);
return null;
}).when(mocked).show(Mockito.anyBoolean());
// -------------------操作对象-----------------------
mocked.show(true);
try {
mocked.data(Map.of("a", 1, "b", 2));
} catch (CustomException e) {
log.info("catch exception => {}", e.getMessage());
}
// 对于没有打桩的属性, 都是null, 唯一例外的state, 是因为他是int, 默认值为0
log.info("Mocked: {}", Jackson.toJSONString(mocked));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
控制台输出内容:
15:12:03.584 [main] INFO c.s.c.c.MockitoTest -- =======Mock=======
15:12:03.597 [main] INFO c.s.c.c.MockitoTest -- mocked.show(true)
15:12:03.597 [main] INFO c.s.c.c.MockitoTest -- catch exception => Mockito.doThrow
15:12:03.599 [main] INFO c.s.c.c.MockitoTest -- Mocked: {"state":0,"now":"2024-11-22 00:00:00.000","show":false}
2
3
4
5
# @Spy 和 @SpyBean
@Spy
是 Mockito 框架中的一个注解, 常用于创建一个部分模拟对象.
@SpyBean
是Spring Boot 应用的集成测试中, 它是 Spring Boot Test 框架提供的一个注解. 它用于在 Spring 应用程序上下文(ApplicationContext)中创建一个间谍类型的 bean. 本质上它和 @Spy
类似, 也是为了能在测试中对某个 bean 的部分方法进行模拟, 同时保持这个 bean 能在 Spring 容器的管理下正常参与整个测试环境的依赖注入等流程.
@Test
public void testSpy() {
log.info("=======Ordinary=======");
Result ordinary = Result.success();
log.info("Ordinary: {}", Jackson.toJSONString(ordinary));
log.info("=======Spy=======");
Result spied = Mockito.spy(ordinary);
// 打桩(拦截已代理的方法, 并返回指定的值)
Mockito.doReturn(201).when(spied).getState();
Mockito.doReturn("Piling").when(spied).getCode();
Mockito.doReturn("Mockito Piling").when(spied).getMessage();
// 没有代理的方法, 还是会执行原有逻辑的
spied.data(Map.of("a", 1)).show();
log.info("Spied: {}", Jackson.toJSONString(spied));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
控制台输出内容:
16:16:46.274 [main] INFO c.s.c.c.MockitoTest -- =======Ordinary=======
16:16:46.398 [main] INFO c.s.c.c.MockitoTest -- Ordinary: {"state":200,"code":"SUCCESS","message":"Success","now":"2024-11-22 16:16:46.275"}
16:16:46.412 [main] INFO c.s.c.c.MockitoTest -- =======Spy=======
16:16:46.417 [main] INFO c.s.c.c.MockitoTest -- Spied: {"state":201,"code":"Piling","message":"Mockito Piling","now":"2024-11-22 16:16:46.275","show":true,"data":{"a":1}}
2
3
4
5
# Mockito.mockStatic
@Test
public void testStatic() {
log.info("=======Ordinary=======");
Command.Result arch = Command.of(List.of("arch")).run();
arch.await();
log.info("Command Run status: {}", arch.getStatus());
log.info("Command Run result: {}", arch.getContent());
// 测试的业务逻辑中, 有需要执行的命令, 但又不能在测试阶段真的执行. 问题来了, Command 对象是在静态方法中new出来的.
log.info("=======Mockito.mockStatic=======");
Command command = Mockito.mock(Command.class);
Command.Result result = Mockito.mock(Command.Result.class);
Mockito.doReturn(Command.Status.SUCCESS).when(result).getStatus();
Mockito.doReturn(List.of("mock result")).when(result).getLines();
Mockito.doNothing().when(result).await();
Mockito.doReturn(result).when(command).run();
Mockito.mockStatic(Command.class).when(() -> Command.of(any())).thenReturn(command);
// 需要注意: Mockito.mockStatic() 只在当前线程生效
Command.Result test = Command.of(List.of("lux", "--help")).run();
test.await();
log.info("mock static run status: {}", test.getStatus());
log.info("mock static run result: {}", test.getLines());
// 下面在其他线程中执行的结果
Thread thread = ThreadKit.run(() -> {
log.info("=======Thread=======");
Command.Result exec = Command.of(List.of("arch")).run();
exec.await();
// 不是我们期望的结果
log.info("exec run status: {}", exec.getStatus());
log.info("exec run result: {}", exec.getContent());
});
ThreadKit.join(thread);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
控制台输出内容:
15:03:34.485 [main] INFO c.s.c.c.MockitoTest -- =======Ordinary=======
15:03:34.502 [main] INFO c.s.c.c.Command -- 执行命令: arch
15:03:34.508 [main] INFO c.s.c.c.MockitoTest -- Command Run status: SUCCESS
15:03:34.508 [main] INFO c.s.c.c.MockitoTest -- Command Run result: arm64
15:03:34.508 [main] INFO c.s.c.c.MockitoTest -- =======Mockito.mockStatic=======
15:03:34.538 [main] INFO c.s.c.c.MockitoTest -- mock static run status: SUCCESS
15:03:34.538 [main] INFO c.s.c.c.MockitoTest -- mock static run result: [mock result]
15:03:34.538 [thread-kit-3] INFO c.s.c.c.MockitoTest -- =======Thread=======
15:03:34.538 [thread-kit-3] INFO c.s.c.c.Command -- 执行命令: arch
15:03:34.542 [thread-kit-3] INFO c.s.c.c.MockitoTest -- exec run status: SUCCESS
15:03:34.542 [thread-kit-3] INFO c.s.c.c.MockitoTest -- exec run result: arm64
15:03:34.553 [main] INFO c.s.c.c.MockitoTest -- =======ThreadKit=======
15:03:34.554 [main] INFO c.s.c.c.MockitoTest -- =======Thread.run() Invalid=======
15:03:34.554 [main] INFO c.s.c.c.MockitoTest -- exec run status: SUCCESS
15:03:34.554 [main] INFO c.s.c.c.MockitoTest -- exec run result: [mock result]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# @InjectMocks
@InjectMocks
是 Mockito 框架中的一个注解. 它的主要作用是将使用@Mock
(或@Spy
)注解创建的模拟对象注入到被测试的目标对象中. 这个被测试的目标对象通常是一个包含其他对象依赖关系的类, @InjectMocks
可以帮助自动完成这些依赖的注入, 从而方便地构建出一个完整的测试环境.
使用 @InjectMocks
时, 要把运行环境配置为: @RunWith(MockitoJUnitRunner.class)
package com.striveonger.common.core;
import com.striveonger.common.core.result.Result;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Mr.Lee
* @since 2024-09-05 11:33
*/
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {
private static final Logger log = LoggerFactory.getLogger(MockitoTest.class);
@Mock
private Result result;
@InjectMocks
private Zliv liv;
@Before
public void setup() {
Mockito.doReturn(201).when(result).getState();
Mockito.doReturn("Piling").when(result).getCode();
Mockito.doReturn("Mockito Piling").when(result).getMessage();
}
@Test
public void test() {
// 会将: result 自动注入到 liv 中
// 从hashcode, 可以看出, 是同一个对象
log.info("result hashcode: {}", result.hashCode());
log.info("liv result hashcode: {}", liv.getResult().hashCode());
log.info("result: {}", Jackson.toJSONString(liv.getResult()));
}
public static class Zliv {
private Result result;
public Result getResult() {
return result;
}
public void setResult(Result result) {
this.result = result;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
控制台输出内容:
16:14:53.669 [main] INFO c.s.c.c.MockitoTest -- result hashcode: 479734028
16:14:53.671 [main] INFO c.s.c.c.MockitoTest -- liv result hashcode: 479734028
16:14:53.771 [main] INFO c.s.c.c.MockitoTest -- result: {"state":201,"code":"Piling","message":"Mockito Piling","show":false}
2
3
4
# 总结
@Mock
与@Spy
的区别: @Mock
代理了目标对象的全部方法, @Spy
只是代理部分方法.
@MockBean
与@SpyBean
都是将其代理的对象, 替换SpringIOC中原有的对象.
@InjectMocks
会注入所依赖的 @Mock
和 @Spy
维护的对象.
参考:
- https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html