
本文详解如何对返回 void 的方法进行有效单元测试,重点解决误用 mockito.spy 和 dothrow 导致的测试失效问题,通过 assertdoesnotthrow 和 assertthrows 实现高覆盖率、高可靠性的断言验证。
在 JUnit 5 中测试 void 方法(如 checkVaultResponseErrors)时,核心原则是:被测方法本身不应被 mock 或 spy —— 它必须真实执行,才能验证其行为(尤其是异常抛出逻辑)。你原测试中使用 Mockito.spy(vaultService) 并配合 doThrow(...).when(...) 实际上拦截并替换了目标方法的真实逻辑,导致 verify() 检查的是 mock 行为而非业务逻辑,代码覆盖率自然为零,且完全偏离了测试目的。
正确的做法是:直接调用被测方法,用 JUnit 5 内置断言捕获并验证其副作用(如异常)。以下是针对 checkVaultResponseErrors 的三个关键测试场景,覆盖全部分支逻辑:
✅ 场景 1:响应状态为 SUCCESS → 不应抛出任何异常
@Test
void checkVaultResponseErrors_whenSuccess_thenNoException() {
// Arrange
VaultResponse vaultResponse = new VaultResponse();
vaultResponse.setResponseStatus(ResponseStatus.SUCCESS);
vaultResponse.setErrors(Collections.emptyList()); // 空错误列表
// Act & Assert
assertDoesNotThrow(() -> vaultService.checkVaultResponseErrors(vaultResponse));
}✅ 场景 2:响应状态为 EXCEPTION → 应抛出 VaultException
@Test
void checkVaultResponseErrors_whenException_thenVaultException() {
// Arrange
VaultResponse vaultResponse = new VaultResponse();
vaultResponse.setResponseStatus(ResponseStatus.EXCEPTION);
List errors = Collections.singletonList(new Error("DB connection timeout"));
vaultResponse.setErrors(errors);
// Act & Assert
VaultException thrown = assertThrows(VaultException.class,
() -> vaultService.checkVaultResponseErrors(vaultResponse)
);
// 可选:验证异常消息是否符合预期
assertTrue(thrown.getMessage().contains("DB connection timeout"));
} ✅ 场景 3:响应状态为 FAILURE → 应抛出 VaultFailure
@Test
void checkVaultResponseErrors_whenFailure_thenVaultFailure() {
// Arrange
VaultResponse vaultResponse = new VaultResponse();
vaultResponse.setResponseStatus(ResponseStatus.FAILURE);
List errors = Collections.singletonList(new Error("Invalid product ID"));
vaultResponse.setErrors(errors);
// Act & Assert
VaultFailure thrown = assertThrows(VaultFailure.class,
() -> vaultService.checkVaultResponseErrors(vaultResponse)
);
assertTrue(thrown.getMessage().contains("Invalid product ID"));
} ⚠️ 关键注意事项:
- 禁止 mock 被测方法本身:spy() + doThrow() 是反模式,它让测试脱离真实逻辑,失去意义;
- 依赖真实对象协作:vaultService 应通过 @Autowired 注入(SpringBootTest)或手动构造(需传入 mocked ProductRepository/DatabaseService),但仅 mock 其依赖项,不 mock 自身方法;
- 覆盖边界条件:确保 errors 列表为 null 时也测试(可添加 vaultResponse.setErrors(null) 场景),避免 NPE;
- 使用 assertThrows 的泛型精准捕获:明确指定期望异常类型,避免误捕父类异常(如 RuntimeException)导致假阳性。
通过以上结构化测试,不仅能达成 100% 分支覆盖率(if (status != SUCCESS)、if (EXCEPTION)、if (FAILURE)),更能真实验证业务规则与异常语义——这才是单元测试的核心价值。









