You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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继承类中的父类方法?

咱们先揪出你测试代码里的几个关键问题:

  1. 语法错误:when((BaseSplitter)Bonus)里把对象实例bonus写成了类名Bonus,类型转换的对象引用完全错了;
  2. 方法名拼写不一致:派生类里调用的splitsalaryHalf是小写开头,和父类的splitSalaryHalf不匹配,这会直接导致编译错误;
  3. 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:当前继承设计是否有误?如何优化?

从设计原则角度看,当前的继承方案确实存在不少问题:

  1. 违反单一职责原则:BaseSplitter既管薪资拆分,又包含abc()这类无关业务方法,职责混乱;
  2. 紧耦合风险:Bonus通过继承绑定到BaseSplitter,后续父类逻辑变更很容易波及子类;
  3. 继承关系不合理:Bonus并不是一种BaseSplitter,它只是需要用到薪资拆分的能力,这种场景更适合用组合而非继承。

优化方案:组合+接口分离职责

咱们可以把不同职责拆分到独立接口,通过组合复用功能,同时保留对abc()等方法的使用:

  1. 定义抽象接口拆分职责
// 薪资拆分行为的接口
interface SalarySplitter {
    int splitSalaryHalf(String personName, int salary);
}

// 其他业务行为的接口
interface OtherBusiness {
    int abc();
}
  1. 让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;
    }
}
  1. 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依赖抽象而非具体实现,后续可以轻松替换不同的拆分器或业务实现;
  • 测试更简单:直接MockSalarySplitter接口即可,完全不用处理继承相关的麻烦。

如果因为历史代码限制无法完全重构,至少可以先把BaseSplitter中的非薪资拆分方法抽离到单独类中,减少Bonus的继承依赖。


内容的提问来源于stack exchange,提问作者nil96

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:06:11