Mockito无法Mock父类方法问题及设计模式合理性咨询
Mockito测试继承类父方法与设计模式优化问题
相关代码
基类
class BaseSplitter{ public int splitSalaryHalf(String personName,int Salary){ //check if person is employee //check is person eligible //some more check if (/* 符合条件 */) { //split salary into salary/2 return Salary/2; } else { return -1; } } public int abc(){ // 父类其他业务逻辑 return 0; } }
派生类
class Bonus extends BaseSplitter{ public int giveBonus(String personName,int salary){ // 修正原代码的方法名拼写错误(splitsalaryHalf → splitSalaryHalf) int bonusAmount = super.splitSalaryHalf(personName, salary); if(bonusAmount == -1){ return 0; } return salary + bonusAmount; } }
测试代码(原错误版本)
import org.junit.Test; import static org.mockito.Mockito.*; import static org.junit.Assert.*; public class BonusTest { @Test public void giveBonus() throws Exception{ Bonus bonus = Mockito.spy(new Bonus()); // 原代码存在语法错误:类型转换和对象引用错误 doReturn(500).when((BaseSplitter)Bonus).splitSalaryHalf("sunil",2000); int num = bonus.giveBonus("sunil",2000); assertEquals(2500,num); } }
问题1:如何Mock继承类中的父类方法?
咱们先揪出你测试代码里的几个关键问题:
- 语法错误:
when((BaseSplitter)Bonus)里把对象实例bonus写成了类名Bonus,类型转换的对象引用完全错了; - 方法名拼写不一致:派生类里调用的
splitsalaryHalf是小写开头,和父类的splitSalaryHalf不匹配,这会直接导致编译错误; - Mockito Spy的用法误区:如果用
when().thenReturn()会触发真实方法执行,这正是你要避免的,必须用doReturn().when()的语法来跳过真实逻辑。
修正后的测试代码
import org.junit.Test; import static org.mockito.Mockito.*; import static org.junit.Assert.*; public class BonusTest { @Test public void giveBonus() throws Exception{ // 创建子类的spy对象 Bonus bonusSpy = spy(new Bonus()); // 正确Mock父类的splitSalaryHalf方法:转换为父类类型后指定行为 doReturn(500).when((BaseSplitter) bonusSpy).splitSalaryHalf("sunil", 2000); // 调用测试方法 int result = bonusSpy.giveBonus("sunil", 2000); // 断言结果 assertEquals(2500, result); } }
另外还有一种不需要Mockito的简便方法:创建局部子类重写父类方法,直接返回预设值:
@Test public void giveBonusWithLocalSubclass() throws Exception{ Bonus testBonus = new Bonus(){ @Override public int splitSalaryHalf(String personName, int Salary) { return 500; } }; int result = testBonus.giveBonus("sunil",2000); assertEquals(2500, result); }
问题2:当前继承设计是否有误?如何优化?
从设计原则角度看,当前的继承方案确实存在不少问题:
- 违反单一职责原则:
BaseSplitter既管薪资拆分,又包含abc()这类无关业务方法,职责混乱; - 紧耦合风险:
Bonus通过继承绑定到BaseSplitter,后续父类逻辑变更很容易波及子类; - 继承关系不合理:
Bonus并不是一种BaseSplitter,它只是需要用到薪资拆分的能力,这种场景更适合用组合而非继承。
优化方案:组合+接口分离职责
咱们可以把不同职责拆分到独立接口,通过组合复用功能,同时保留对abc()等方法的使用:
- 定义抽象接口拆分职责
// 薪资拆分行为的接口 interface SalarySplitter { int splitSalaryHalf(String personName, int salary); } // 其他业务行为的接口 interface OtherBusiness { int abc(); }
- 让BaseSplitter实现这些接口
class BaseSplitter implements SalarySplitter, OtherBusiness { @Override public int splitSalaryHalf(String personName, int salary) { // 原有的复杂校验逻辑 if (/* 符合条件 */) { return salary/2; } else { return -1; } } @Override public int abc() { // 原有的业务逻辑 return 0; } }
- Bonus类通过组合注入依赖
class Bonus { private SalarySplitter salarySplitter; private OtherBusiness otherBusiness; // 构造注入依赖,方便测试时替换实现 public Bonus(SalarySplitter salarySplitter, OtherBusiness otherBusiness) { this.salarySplitter = salarySplitter; this.otherBusiness = otherBusiness; } public int giveBonus(String personName, int salary) { int bonusAmount = salarySplitter.splitSalaryHalf(personName, salary); if (bonusAmount == -1) { return 0; } // 需要使用abc()时直接调用组合的实例 otherBusiness.abc(); return salary + bonusAmount; } }
这样优化的好处:
- 职责清晰,每个类/接口只负责单一功能;
- 耦合度大幅降低,
Bonus依赖抽象而非具体实现,后续可以轻松替换不同的拆分器或业务实现; - 测试更简单:直接Mock
SalarySplitter接口即可,完全不用处理继承相关的麻烦。
如果因为历史代码限制无法完全重构,至少可以先把BaseSplitter中的非薪资拆分方法抽离到单独类中,减少Bonus的继承依赖。
内容的提问来源于stack exchange,提问作者nil96
相关产品推荐
相关产品推荐

