单元测试-junit5的spy部分mock

发布于:2025-09-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

使用 JUnit 5 进行 Spy 和 Mock 测试

在 JUnit 5 中,结合 Mockito 库可以轻松实现 Spy(部分模拟)和 Mock(完全模拟)的功能。Mockito 提供 @Mock@Spy 注解,配合 JUnit 5 的扩展机制,能高效完成单元测试。

依赖配置

确保项目中包含以下依赖(以 Maven 为例):

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.5.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.5.1</version>
    <scope>test</scope>
</dependency>

Mock 对象的使用

@Mock 用于创建完全模拟的对象,所有方法默认返回空值或默认值,除非显式定义行为。

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class MockTest {

    @Mock
    private List<String> mockedList;

    @Test
    void testMockBehavior() {
        mockedList.add("item");
        verify(mockedList).add("item"); // 验证方法调用
        when(mockedList.size()).thenReturn(100); // 定义返回值
        assertEquals(100, mockedList.size());
    }
}

Spy 对象的使用

@Spy 用于创建部分模拟对象,保留真实对象的行为,仅对特定方法进行模拟。

@ExtendWith(MockitoExtension.class)
public class SpyTest {

    @Spy
    private List<String> spiedList = new ArrayList<>();

    @Test
    void testSpyRealMethod() {
        spiedList.add("real-item");
        assertEquals(1, spiedList.size()); // 调用真实方法
    }

    @Test
    void testSpyMockedMethod() {
        doReturn(100).when(spiedList).size(); // 模拟方法
        assertEquals(100, spiedList.size());
    }
}

关键区别与注意事项

  • Mock:所有方法均需手动定义行为,未定义的方法返回默认值(如 null0)。
  • Spy:基于真实对象,仅对需要的方法进行覆盖,未模拟的方法执行真实逻辑。
  • 初始化@Spy 需依赖真实对象实例(如直接赋值或通过 @BeforeEach 初始化),而 @Mock 自动生成空实例。
  • 语法差异
    • Mock 的存根语法:when(mock.method()).thenReturn(value)
    • Spy 的存根语法:doReturn(value).when(spy).method()

常见场景示例

验证方法调用次数:

@Test
void verifyInteraction() {
    spiedList.add("item");
    verify(spiedList, times(1)).add(anyString());
}

模拟异常抛出:

@Test
void mockException() {
    when(mockedList.get(0)).thenThrow(new RuntimeException());
    assertThrows(RuntimeException.class, () -> mockedList.get(0));
}

非注解方式实现 Spy

直接通过 Mockito.spy() 方法创建 spy 对象:

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

class ManualSpyTest {
    @Test
    void testManualSpy() {
        List<String> realList = new ArrayList<>();
        List<String> spyList = spy(realList); // 手动创建 spy

        spyList.add("real");
        spyList.add("data");

        // 部分模拟:size 方法返回固定值
        when(spyList.size()).thenReturn(100);

        assertEquals(2, spyList.size()); // 实际返回 100
        assertTrue(spyList.contains("real")); // 真实调用
    }
}

关键区别与注意事项

  1. 注解方式
    依赖 @ExtendWith(MockitoExtension.class),通过 @Spy 自动初始化 spy 对象。需手动实例化被 spy 的对象(如 new ArrayList<>()),否则会抛出异常。

  2. 非注解方式
    通过 Mockito.spy() 手动创建 spy,适合需要动态控制 spy 对象的场景。

  3. 部分模拟行为
    默认调用真实方法,仅对显式声明的方法进行模拟。以下代码会调用真实方法:

// 未模拟的方法会执行真实逻辑
spyList.add("test"); 
  1. 避免 final 方法
    Mockito 无法 spy final 方法或类(如 String),需结合 PowerMock 等工具扩展。

常见问题解决

  1. 初始化问题
    注解方式中,若 @Spy 对象未实例化会报错:
@Spy // 错误!未初始化
private List<String> spyList; 

@Spy // 正确
private List<String> spyList = new ArrayList<>();
  1. 静态方法模拟
    Spy 不适用于静态方法,需配合 mockStatic(Mockito 3.4+):
try (MockedStatic<MyClass> mocked = mockStatic(MyClass.class)) {
    mocked.when(MyClass::staticMethod).thenReturn("mocked");
}