单元测试-junit5的spy部分mock
使用 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 {@Mockprivate List<String> mockedList;@Testvoid 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 {@Spyprivate List<String> spiedList = new ArrayList<>();@Testvoid testSpyRealMethod() {spiedList.add("real-item");assertEquals(1, spiedList.size()); // 调用真实方法}@Testvoid testSpyMockedMethod() {doReturn(100).when(spiedList).size(); // 模拟方法assertEquals(100, spiedList.size());}
}
关键区别与注意事项
- Mock:所有方法均需手动定义行为,未定义的方法返回默认值(如
null
或0
)。 - Spy:基于真实对象,仅对需要的方法进行覆盖,未模拟的方法执行真实逻辑。
- 初始化:
@Spy
需依赖真实对象实例(如直接赋值或通过@BeforeEach
初始化),而@Mock
自动生成空实例。 - 语法差异:
- Mock 的存根语法:
when(mock.method()).thenReturn(value)
- Spy 的存根语法:
doReturn(value).when(spy).method()
- Mock 的存根语法:
常见场景示例
验证方法调用次数:
@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 {@Testvoid testManualSpy() {List<String> realList = new ArrayList<>();List<String> spyList = spy(realList); // 手动创建 spyspyList.add("real");spyList.add("data");// 部分模拟:size 方法返回固定值when(spyList.size()).thenReturn(100);assertEquals(2, spyList.size()); // 实际返回 100assertTrue(spyList.contains("real")); // 真实调用}
}
关键区别与注意事项
-
注解方式
依赖@ExtendWith(MockitoExtension.class)
,通过@Spy
自动初始化 spy 对象。需手动实例化被 spy 的对象(如new ArrayList<>()
),否则会抛出异常。 -
非注解方式
通过Mockito.spy()
手动创建 spy,适合需要动态控制 spy 对象的场景。 -
部分模拟行为
默认调用真实方法,仅对显式声明的方法进行模拟。以下代码会调用真实方法:
// 未模拟的方法会执行真实逻辑
spyList.add("test");
- 避免 final 方法
Mockito 无法 spy final 方法或类(如String
),需结合 PowerMock 等工具扩展。
常见问题解决
- 初始化问题
注解方式中,若@Spy
对象未实例化会报错:
@Spy // 错误!未初始化
private List<String> spyList; @Spy // 正确
private List<String> spyList = new ArrayList<>();
- 静态方法模拟
Spy 不适用于静态方法,需配合mockStatic
(Mockito 3.4+):
try (MockedStatic<MyClass> mocked = mockStatic(MyClass.class)) {mocked.when(MyClass::staticMethod).thenReturn("mocked");
}