Mockito


在这里插入图片描述

依赖

       <!-- 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

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

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

静态方法单元测试

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 注解的单元测试:

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 等)或其对应的包装类型。

使用场景:
测试某个方法或函数在一系列特定值上的表现,如验证数学函数在不同整数、浮点数上的计算结果。
验证类的构造函数或方法对各种边界值(如最大值、最小值、零值、正负值等)的处理是否正确。

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

使用场景:
对枚举类型的类进行全面测试,确保其每个枚举常量在特定场景下的行为正确。
当测试方法需要验证针对枚举类型的逻辑时,如序列化、反序列化、字符串转换等。

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 格式。

@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 对象封装了一组参数。这种方法提供了最大的灵活性,可以生成动态数据、读取外部资源、甚至执行复杂的逻辑来提供测试参数。

使用场景:
数据集较大或需要动态生成时,可以编写一个方法来按需生成或读取数据。
测试需要依赖外部配置文件、数据库查询结果、随机数据生成器等复杂数据源。
需要根据测试环境动态调整测试参数。

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():

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

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 接口的对象)。当被测方法接受一个列表作为参数,但不关心列表的具体元素时使用。

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 接口的对象)。当被测方法接受一个集合作为参数,但不关心集合的具体元素时使用。

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 接口的对象。当被测方法接受一个可迭代对象作为参数,但不关心其具体元素时使用。

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 接口的对象)。当被测方法接受一个映射作为参数,但不关心其具体键值对时使用。

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 对象
}
<think>嗯,用户想了解Mockito的使用方法和示例代码。首先,我需要回顾Mockito的基本概念,比如模拟对象、桩方法、验证交互等。记得用户可能需要从安装配置开始,所以应该先介绍如何添加Mockito依赖,比如Maven或Gradle的配置。然后,分步骤讲解创建模拟对象的不同方法,比如使用@Mock注解或者Mockito.mock()方法。 接下来是桩方法设置,这部分很重要,用户通常需要知道如何让模拟对象返回特定的值或者抛出异常。要举一些具体的例子,比如when(...).thenReturn(...)的结构,可能还要提到thenThrow和thenAnswer的用法。 然后是验证交互的部分,用户可能想知道如何检查方法是否被调用,调用次数是否正确。需要解释verify的基本用法,以及使用参数匹配器如any()、eq()的注意事项。 还有,用户提到参考了引用中的代码,特别是使用@Mock和@InjectMocks的示例,所以需要详细说明这些注解的使用步骤,包括初始化注解的方式,比如MockitoAnnotations.openMocks(this)或者JUnit的MockitoExtension。 另外,可能需要提到Mockito的一些高级功能,比如参数捕获、部分模拟,或者与Spring框架的集成,但根据用户的问题,可能基础内容更重要。需要确保示例代码正确,语法无误,特别是Java和可能的Kotlin代码,不过用户提供的引用中有Kotlin项目地址,但问题主要是Java的示例,所以重点放在Java上。 最后,生成的相关问题要覆盖用户可能进一步想知道的内容,比如与JUnit5的整合、异常测试、Spring集成、参数捕获等。确保每个步骤都有清晰的说明,代码示例正确,并且引用正确对应的参考来源。</think>### Mockito 使用指南 Mockito 是一个流行的 Java 测试框架,用于模拟依赖对象的行为,简化单元测试。以下是核心使用方法和示例: --- #### 1. **添加依赖** 在项目中引入 Mockito(以 Maven 为例): ```xml <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> ``` 若使用 Kotlin,可参考 `mockito-kotlin` 扩展库[^1]。 --- #### 2. **创建模拟对象** **方式一:通过注解** ```java public class CustomerServiceTest { @Mock private CustomerDao daoMock; // 模拟对象 @InjectMocks private CustomerService service; // 自动注入依赖 @Before public void setUp() { MockitoAnnotations.openMocks(this); // 初始化注解 } } ``` **方式二:通过静态方法** ```java CustomerDao daoMock = Mockito.mock(CustomerDao.class); ``` --- #### 3. **桩方法(Stubbing)** 定义模拟对象的行为: ```java // 当调用 daoMock.findById(1L) 时返回预设对象 Mockito.when(daoMock.findById(1L)) .thenReturn(new Customer("Alice")); // 抛出异常 Mockito.when(daoMock.delete(any())) .thenThrow(new RuntimeException()); // 动态返回 Mockito.when(daoMock.getName()) .thenAnswer(invocation -> "Response"); ``` --- #### 4. **验证交互** 检查方法是否按预期调用: ```java // 验证 daoMock.save() 被调用一次 Mockito.verify(daoMock, times(1)).save(any(Customer.class)); // 验证未调用 Mockito.verify(daoMock, never()).deleteAll(); ``` --- #### 5. **参数匹配器** 灵活匹配输入参数: ```java Mockito.when(daoMock.findByAge(Mockito.anyInt())) // 任意整数 .thenReturn(new Customer()); Mockito.verify(daoMock).update(Mockito.eq("id123"), any()); // 精确匹配第一个参数 ``` --- #### 6. **高级用法示例** **捕获参数值:** ```java ArgumentCaptor<Customer> captor = ArgumentCaptor.forClass(Customer.class); Mockito.verify(daoMock).save(captor.capture()); Customer capturedCustomer = captor.getValue(); ``` **部分模拟(Spy):** ```java List<String> realList = new ArrayList<>(); List<String> spyList = Mockito.spy(realList); // 覆盖部分方法 Mockito.when(spyList.size()).thenReturn(100); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

With Order @!147

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值