1. 概述
模拟(Mocking)是单元测试中非常实用的功能,特别是在隔离测试方法中单一行为时。本教程将演示如何使用Mockito Spy模拟同一测试类中的方法。
2. 问题理解
单元测试是测试的第一道防线,能有效拦截早期bug。但有时方法过于复杂(尤其是遗留代码中),包含多个方法调用,且重构机会有限。我们将通过一个简单案例展示Mockito Spy的威力。
示例类CatTantrum
:
public class CatTantrum {
public enum CatAction {
BITE,
MEOW,
VOMIT_ON_CARPET,
EAT_DOGS_FOOD,
KNOCK_THING_OFF_TABLE
}
public enum HumanReaction {
SCREAM,
CRY,
CLEAN,
PET_ON_HEAD,
BITE_BACK,
}
public HumanReaction whatIsHumanReaction(CatAction action){
return switch (action) {
case MEOW -> HumanReaction.PET_ON_HEAD;
case VOMIT_ON_CARPET -> HumanReaction.CLEAN;
case EAT_DOGS_FOOD -> HumanReaction.SCREAM;
case KNOCK_THING_OFF_TABLE -> HumanReaction.CRY;
case BITE -> biteCatBack();
};
}
public HumanReaction biteCatBack() {
// Some logic
return HumanReaction.BITE_BACK;
}
}
方法whatIsHumanReaction()
包含业务逻辑并调用了biteCatBack()
方法。我们将重点测试这个方法。
3. 使用Mockito Spy的解决方案
编写单元测试:
public void givenMockMethodHumanReactions_whenCatActionBite_thenHumanReactionsBiteBack(){
// Given
CatTantrum catTantrum = new CatTantrum();
CatTantrum catTantrum1 = Mockito.spy(catTantrum);
Mockito.doReturn(HumanReaction.BITE_BACK).when(catTantrum1).biteCatBack();
// When
HumanReaction humanReaction1 = catTantrum1.whatIsHumanReaction(CatAction.BITE);
// Then
assertEquals(humanReaction1, HumanReaction.BITE_BACK);
}
关键步骤解析:
- ✅ 创建spy对象:
Mockito.spy(catTantrum)
允许我们模拟被调用方法biteCatBack()
的返回值 - ⚠️ 调用测试方法:必须在spy对象
catTantrum1
上调用测试方法,而非原始对象 - ✅ 混合模式优势:
- 直接调用真实方法
whatIsHumanReaction()
- 同时模拟
biteCatBack()
的返回值
- 直接调用真实方法
Mockito Spy的核心价值:
- 部分模拟:既可调用真实方法,又可存根特定方法
- 避免全量存根:相比mock无需存根所有方法
- 突破限制:相比真实对象能灵活模拟特定行为
4. 总结
本文展示了使用Mockito Spy解决同一测试类中方法模拟问题的方案。最佳实践是优先编写简洁、单一职责的方法,但当遇到复杂遗留代码时,Mockito Spy提供了简单粗暴的解决方案,能有效绕过测试障碍。