Striveonger

vuePress-theme-reco Mr.Lee    2015 - 2025
Striveonger Striveonger
主页
分类
  • 文章
  • 笔记
  • 工具
标签
时间轴
author-avatar

Mr.Lee

264

Article

134

Tag

主页
分类
  • 文章
  • 笔记
  • 工具
标签
时间轴

浅尝 Mockito

vuePress-theme-reco Mr.Lee    2015 - 2025

浅尝 Mockito

Mr.Lee 2024-11-23 21:26:00 JavaMockito

浅尝一下 Mockito

# Mockito

# 构建测试用例

@Test
public void testOrdinary() {
    log.info("=======Ordinary=======");
    Result ordinary = Result.success();
    log.info("Ordinary: {}", Jackson.toJSONString(ordinary));
}
1
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"}

1
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));
}
1
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}

1
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));
}
1
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}}

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);
}
1
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]
1
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;
        }
    }
}
1
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}

1
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