使用 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:所有方法均需手动定义行为,未定义的方法返回默认值(如
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 {
@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")); // 真实调用
}
}
关键区别与注意事项
注解方式
依赖@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");
}