Mockito

小王学习录

依赖

java 复制代码
       <!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>

注解

Mock

java 复制代码
class MockTestTest {
    @Mock
    private MockTest demo;
    @BeforeEach
    void setup(){
        MockitoAnnotations.openMocks(this);
    }
    @Test
    void add() {
        Mockito.when(demo.add(1, 2)).thenReturn(3);
        System.out.println(demo.add(1, 2));
        Assertions.assertEquals(3, demo.add(1, 2));
        Mockito.verify(demo, Mockito.times(2)).add(1, 2);
    }
    @AfterEach
    void after(){
        System.out.println("测试完毕 ");
    }
}

@Spy

java 复制代码
class SpyTestTest {
    @Spy
    private SpyTest spyTest;
    @BeforeEach
    void setUp(){
        MockitoAnnotations.openMocks(this);
    }
    @Test
    void add() {
        Mockito.when(spyTest.add(1, 2)).thenReturn(4);
        System.out.println(spyTest.add(1, 2));
        Mockito.verify(spyTest).add(1,2);
        Assertions.assertEquals(4, spyTest.add(1, 2));
        //Mockito.verify(spyTest, Mockito.times(2)).add(1, 2);
        Mockito.when(spyTest.add(1, 2)).thenCallRealMethod();
        System.out.println(spyTest.add(1, 2));
    }
}

静态方法单元测试

java 复制代码
class StaticTestTest {
    @Test
    void add() {
        MockedStatic<StaticTest> test1 = Mockito.mockStatic(StaticTest.class);
        test1.when(()->StaticTest.add(1, 2)).thenReturn(4);
        System.out.println(StaticTest.add(1, 2));
        Assertions.assertEquals(4, StaticTest.add(1, 2));
    }
}

@InjectMocks 注解

@Captor 注解

@Captor 是 Mockito 框架中的一个注解,用于创建 ArgumentCaptor 实例。ArgumentCaptor 在单元测试中扮演着重要角色,它允许你在测试过程中捕获(capture)被测方法调用时传入的实际参数值,以便对这些参数进行详细的断言验证。

假设有一个 EmailService 类,它有一个方法 sendEmail(Recipient recipient, EmailMessage message),我们想要测试这个方法是否正确地将邮件发送给了预期的收件人。可以编写如下使用 @Captor 注解的单元测试:

java 复制代码
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.verify;

public class EmailServiceTest {

    @Mock
    private EmailGateway emailGateway; // Mocked dependency for sending emails

    private EmailService emailService;

    @Captor
    private ArgumentCaptor<Recipient> recipientCaptor;

    @Captor
    private ArgumentCaptor<EmailMessage> messageCaptor;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.initMocks(this);
        emailService = new EmailService(emailGateway);
    }

    @Test
    public void testSendEmail() {
        Recipient expectedRecipient = new Recipient("john.doe@example.com");
        EmailMessage expectedMessage = new EmailMessage("Subject", "Body");

        emailService.sendEmail(expectedRecipient, expectedMessage);

        // 使用 captors 捕获实际传递给 sendEmail 方法的参数
        verify(emailGateway).sendEmail(recipientCaptor.capture(), messageCaptor.capture());

        // 使用 captured 参数进行断言
        Recipient actualRecipient = recipientCaptor.getValue();
        EmailMessage actualMessage = messageCaptor.getValue();

        assertEquals(expectedRecipient.getEmail(), actualRecipient.getEmail());
        assertEquals(expectedMessage.getSubject(), actualMessage.getSubject());
        assertEquals(expectedMessage.getBody(), actualMessage.getBody());
    }
}
  1. 使用 @Mock 注解创建了 EmailGateway 的 mock 对象。
  2. 使用 @Captor 注解声明了两个 ArgumentCaptor 实例,分别对应 sendEmail 方法的 Recipient 和 EmailMessage 参数。
  3. 在 setUp 方法中初始化 mock 和被测试的 EmailService。
  4. 在 testSendEmail 测试方法中,调用 emailService.sendEmail() 方法,传入期望的参数。
  5. 使用 verify 方法验证 emailGateway.sendEmail() 是否被调用,并且使用 captors 捕获实际传递的参数。
  6. 最后,通过 captors 获取捕获的参数值,并进行详细的断言验证,确保它们与预期值相匹配。
  7. 通过 @Captor 注解和 ArgumentCaptor,我们能够有效地验证 sendEmail 方法是否正确地将预期的收件人和邮件内容传递给了依赖的 EmailGateway。

@BeforeAll 和 BeforeEach的区别

@BeforeAll 和 @BeforeEach 是 JUnit 5 中用于在测试执行前进行初始化工作的两个注解,它们的主要区别在于执行时机和适用场景:

@BeforeAll

执行时机:

@BeforeAll 注解的方法将在当前测试类中所有测试方法执行之前只执行一次。

适用场景:

  • 一次性昂贵资源的初始化:当测试类中的所有测试方法都需要共享一个昂贵的资源(如数据库连接、网络连接、第三方服务客户端等),并且创建或连接这个资源的成本较高时,使用 @BeforeAll 来初始化一次,避免在每个测试方法前重复执行。
  • 全局数据加载:如果有一组测试数据需要从文件、数据库或其他外部源加载,且所有测试方法都会使用同一份数据,那么可以使用 @BeforeAll 来提前加载数据,避免多次加载带来的性能开销。
  • 长时间运行的预设条件:当有一些耗时较长但对所有测试方法通用的预设条件(如模拟复杂环境、配置全局系统状态等),应放在 @BeforeAll 注解的方法中执行。
  • 静态环境配置:如果需要对静态变量、系统属性或全局配置进行一次性设定,适用于 @BeforeAll。
    注意事项:

@BeforeAll 注解的方法必须是静态方法,因为它们在测试类实例化之前执行。

如果这些方法抛出异常,所有测试方法都将跳过执行。

@BeforeEach

执行时机:

@BeforeEach 注解的方法将在每个测试方法执行之前单独执行一次。

适用场景:

测试数据的独立准备:每个测试方法可能需要不同的测试数据或对象状态,使用 @BeforeEach 可以为每个测试方法单独准备所需的测试数据或对象实例。

对象重置:如果被测试对象具有状态,且每次测试方法执行后需要将其状态重置为初始状态,@BeforeEach 方法可以负责这项清理和重置工作。

局部依赖注入:对于那些仅在一个测试方法执行期间有效的依赖项,可以在 @BeforeEach 方法中注入。

重复性环境配置:对于每次测试开始时都需要重新配置的环境设置(如清除缓存、重置模拟对象状态等),应在 @BeforeEach 注解的方法中进行。

注意事项:

@BeforeEach 注解的方法不能是静态的,因为它们与每个测试方法的执行紧密相关,通常需要访问非静态的成员变量或方法。

如果某个 @BeforeEach 方法执行失败(抛出异常),仅影响紧随其后的那个测试方法,其他测试方法仍会尝试执行各自的 @BeforeEach 方法。

总结来说,@BeforeAll 用于执行一次即可满足整个测试类需求的、成本较高的初始化操作,适用于共享资源的设置和全局数据的加载;而 @BeforeEach 则是在每个测试方法执行前进行针对性的、可能因测试方法不同而有所差异的准备工作,确保每个测试方法在独立、一致的环境中运行。选择使用哪一个注解取决于测试场景的具体需求和资源管理策略。

@ParameterizedTest

@ParameterizedTest 是 JUnit 5 提供的一种用于执行参数化测试的注解。参数化测试允许同一个测试方法使用不同的输入数据集(参数)多次执行,从而提高测试覆盖率和代码复用性。通过这种方式,可以轻松地验证被测代码在多种不同情况下的行为,确保其正确性和一致性。

通常需要配合@ValueSource、@EnumSource、@CsvSource、@MethodSource一起使用。

@ValueSource

用途:@ValueSource 用于为参数化测试提供一组固定的值。这些值可以是基本类型(如 int、double、String 等)或其对应的包装类型。

使用场景:

测试某个方法或函数在一系列特定值上的表现,如验证数学函数在不同整数、浮点数上的计算结果。

验证类的构造函数或方法对各种边界值(如最大值、最小值、零值、正负值等)的处理是否正确。

java 复制代码
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testSquareRoot(int input, int expectedOutput) {
    double result = Math.sqrt(input);
    assertEquals(expectedOutput, result, 0.01, "Square root of " + input + " should be close to " + expectedOutput);
}

@EnumSource

用途:@EnumSource 专门用于为参数化测试提供枚举类型的值。它可以指定要使用的枚举类,并可以选择性地提供筛选条件(如包含特定名称或值的枚举常量)。

使用场景:

对枚举类型的类进行全面测试,确保其每个枚举常量在特定场景下的行为正确。

当测试方法需要验证针对枚举类型的逻辑时,如序列化、反序列化、字符串转换等。

java 复制代码
enum Color { RED, GREEN, BLUE }

@ParameterizedTest
@EnumSource(Color.class)
void testColorToString(Color color, String expectedString) {
    String result = color.toString();
    assertEquals(expectedString, result, "toString() should return the correct string for " + color);
}

@CsvSource

用途:@CsvSource 用于提供以逗号分隔值(CSV)格式的数据集。每一行代表一组参数,各参数间以逗号分隔。这对于多参数的测试方法非常方便,可以直接在注解中列出多行数据。

使用场景:

当测试需要多组不同参数组合时,如验证数学运算、字符串处理函数、日期格式转换等。

当测试数据以表格形式存储或展示时,可以直接复制粘贴成 CSV 格式。

java 复制代码
@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "10, 5, 15",
    "-1, .jpg, .png",
    "abc, def, abcdef"
})
void testConcatenate(String part1, String part2, String expectedConcatenation) {
    String result = concatenate(part1, part2);
    assertEquals(expectedConcatenation, result, "Concatenation of " + part1 + " and " + part2 + " should be " + expectedConcatenation);
}

@MethodSource

用途:@MethodSource 允许参数化测试的数据来源于测试类中的一个静态方法。这个方法返回一个 Stream,其中每个 Arguments 对象封装了一组参数。这种方法提供了最大的灵活性,可以生成动态数据、读取外部资源、甚至执行复杂的逻辑来提供测试参数。

使用场景:

数据集较大或需要动态生成时,可以编写一个方法来按需生成或读取数据。

测试需要依赖外部配置文件、数据库查询结果、随机数据生成器等复杂数据源。

需要根据测试环境动态调整测试参数。

java 复制代码
static Stream<Arguments> additionTestData() {
    return Stream.of(
            Arguments.of(1, 2, 3),
            Arguments.of(-1, 3, 2),
            Arguments.of(0, 0, 0),
            Arguments.of(100, -50, 50),
            Arguments.of(1.5, 2.75, 4.25)
    );
}

 @ParameterizedTest
@MethodSource("additionTestData")
void testAdd(double num1, double num2, double expectedSum) {
    double result = calculator.add(num1, num2);
    assertEquals(expectedSum, result, "The sum should be correct for inputs: " + num1 + ", " + num2);
}

总结起来,@ValueSource、@EnumSource、@CsvSource 和 @MethodSource 分别提供了不同类型和来源的参数数据集

打桩

打桩方式

1、when().thenReturn()

2、doReturn().when()

以上两种是有返回值的方法打桩,通常使用。

3、doNothing().when()...

void方法打桩;

4、when().thenThrow()

5、doThrow().when()

以上两种是抛异常打桩;

6、when().thenAnswer()

7、doAnswer().when()

以上两种是复杂参数打桩,可以根据传参自定义返回结果。

8、@InjectMocks+@Spy,打桩被测类的某个方法

正常被单测对象使用@InjectMocks注解,被测业务逻辑复杂,比如被测方法A()→B()→C()。如果当前单元测试用例不打算测试到C()方法,就需要结合@Spy和具体打桩方式跳过C()方法。

打桩参数匹配方式

打桩时,如果被测方法有输入参数,有以下方式传参:

any()、anyList()、anyByte()、anyBoolean()、anyChar()、anyCollection()、anyDouble()、anyFloat()、anyInt()、anyIterable()、anyLong()、anyMap()、anySet()、anyShort()、anyString()

通常仅需要any(),如果被打桩的测试方法有重载,则需要指定具体的参数类型。

这些传参方法是Mockito框架中用于创建"任意匹配器"的工具,它们在编写单元测试时非常有用,特别是当我们要验证方法被调用时的参数类型和数量,而不关心具体的参数值时。简单举几个例子

  1. any():

用途:匹配任何类型的对象。当被测方法接受的对象类型不确定或不关心其具体值时使用。

java 复制代码
import static org.mockito.Mockito.*;

class Service {
    void process(Object input) {
        // ...
    }
}

@Test
void testProcessMethod() {
    Service service = mock(Service.class);
    service.process(any()); // 匹配任何类型的对象作为输入参数

    verify(service).process(any()); // 验证process方法被调用时,传入了任意对象
}
  1. anyList():

用途:匹配任何类型的列表(实现 List 接口的对象)。当被测方法接受一个列表作为参数,但不关心列表的具体元素时使用。

java 复制代码
import static org.mockito.Mockito.*;

class Service {
    void handleList(List<String> items) {
        // ...
    }
}

@Test
void testHandleListMethod() {
    Service service = mock(Service.class);
    service.handleList(anyList()); // 匹配任何类型的列表作为输入参数

    verify(service).handleList(anyList()); // 验证handleList方法被调用时,传入了任意列表
}
  1. anyCollection():

用途:匹配任何类型的 Collection 对象(实现了 Collection 接口的对象)。当被测方法接受一个集合作为参数,但不关心集合的具体元素时使用。

java 复制代码
import static org.mockito.Mockito.*;

class Service {
    void handleCollection(Collection<String> items) {
        // ...
    }
}

@Test
void testHandleCollectionMethod() {
    Service service = mock(Service.class);
    service.handleCollection(anyCollection()); // 匹配任何类型的 Collection 对象作为输入参数

    verify(service).handleCollection(anyCollection()); // 验证handleCollection方法被调用时,传入了任意 Collection 对象
}
  1. anyIterable():

用途:匹配任何实现了 Iterable 接口的对象。当被测方法接受一个可迭代对象作为参数,但不关心其具体元素时使用。

java 复制代码
import static org.mockito.Mockito.*;

class Service {
    void processIterable(Iterable<String> items) {
        // ...
    }
}

@Test
void testProcessIterableMethod() {
    Service service = mock(Service.class);
    service.processIterable(anyIterable()); // 匹配任何 Iterable 对象作为输入参数

    verify(service).processIterable(anyIterable()); // 验证processIterable方法被调用时,传入了任意 Iterable 对象
}
  1. anyMap():

用途:匹配任何类型的 Map 对象(实现了 Map 接口的对象)。当被测方法接受一个映射作为参数,但不关心其具体键值对时使用。

java 复制代码
import static org.mockito.Mockito.*;

class Service {
    void updateData(Map<String, Integer> data) {
        // ...
    }
}

@Test
void testUpdateDataMethod() {
    Service service = mock(Service.class);
    service.updateData(anyMap()); // 匹配任何 Map 对象作为输入参数

    verify(service).updateData(anyMap()); // 验证updateData方法被调用时,传入了任意 Map 对象
}
相关推荐
Dnelic-3 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记
岳哥i6 小时前
前端项目接入单元测试手册
前端·单元测试
qq_4337169520 小时前
Selenium+Pytest自动化测试框架 ------ 禅道实战
自动化测试·软件测试·selenium·单元测试·pytest·接口测试·压力测试
Dreams°1231 天前
【大数据测试ETL:从0-1实战详细教程】
大数据·数据仓库·python·单元测试·etl
敲代码敲到头发茂密1 天前
怎么做好白盒测试?
java·数据库·mysql·算法·单元测试·模块测试·测试覆盖率
武昌库里写JAVA2 天前
最新6.7分非肿瘤纯生信,使用机器学习筛选慢阻肺中的关键基因。机器学习在非肿瘤生信文章中正火,可重复!
java·开发语言·算法·spring·log4j
Dreams°1234 天前
【大数据测试HDFS + Flask详细教程与实例】
大数据·功能测试·hdfs·单元测试·flask
卷心菜是俺5 天前
Sping全面复习
java·开发语言·数据库·junit·java-ee·log4j·maven
武昌库里写JAVA5 天前
R语言机器学习与临床预测模型77--机器学习预测常用R语言包
java·开发语言·算法·spring·log4j
CSXB995 天前
三十八、Python(pytest框架-上)
python·功能测试·测试工具·单元测试·pytest