
本文讲解如何对继承自父类的子类进行单元测试,当待测公共/受保护方法内部调用父类受保护方法时,应避免直接 mock 子类本身,而是通过构造注入+mock 依赖的方式解耦测试,提升可测性与设计合理性。
本文讲解如何对继承自父类的子类进行单元测试,当待测公共/受保护方法内部调用父类受保护方法时,应避免直接 mock 子类本身,而是通过构造注入+mock 依赖的方式解耦测试,提升可测性与设计合理性。
在 Java 单元测试(尤其是使用 Mockito)中,一个常见误区是:为测试子类而试图 mock 子类自身(如使用 @InjectMocks + @Mock 组合),或强行 mock 抽象父类。这不仅违背测试原则,还极易引发 IllegalStateException 等运行时异常(如示例中的 "user not set"),因为 mock 对象无法真实执行继承链中的初始化逻辑(如 getUser() 依赖未设置的状态)。
正确的测试策略是 面向协作而非继承:将原本的“is-a”关系(继承)重构为“has-a”关系(组合),从而让被测类(DataSource)的依赖(ExtendedDataSource)可被完全控制和模拟。
✅ 推荐实践:改用构造注入 + 依赖抽象化
首先重构 DataSource 类,放弃继承 ExtendedDataSource,改为持有其引用:
public class DataSource {
private final ExtendedDataSource extendedDataSource;
public DataSource(ExtendedDataSource extendedDataSource) {
this.extendedDataSource = Objects.requireNonNull(extendedDataSource);
}
public Connection setup(Properties props) {
return extendedDataSource.getConnection(props);
}
}? 提示:
ExtendedDataSource应定义为接口或抽象类(推荐接口),以明确契约、降低耦合。若暂不能改接口,至少确保其方法可被 Mockito 模拟(非 final、非 static、非 private)。
✅ 编写可信赖的测试用例
class DataSourceTest {
@Test
void setup_returnsConnection_whenExtendedDataSourceReturnsValidConnection() {
// Given
Properties props = new Properties();
Connection mockConnection = mock(Connection.class);
ExtendedDataSource mockExtendedDS = mock(ExtendedDataSource.class);
when(mockExtendedDS.getConnection(eq(props))).thenReturn(mockConnection);
DataSource dataSource = new DataSource(mockExtendedDS); // 构造注入依赖
// When
Connection result = dataSource.setup(props);
// Then
assertThat(result).isSameAs(mockConnection);
verify(mockExtendedDS).getConnection(eq(props));
}
}⚠️ 关键注意事项
-
不要 mock 被测类本身:
@InjectMocks DataSource dataSource是错误用法。@InjectMocks仅适用于 需要自动注入 mock 依赖的被测对象,而非被测类的实例;此处DataSource应显式new创建。 -
避免 mock 抽象基类(如
AbstractSource):它通常承载核心状态(如user字段),mock 后getUser()将返回null,导致IllegalStateException。组合模式天然规避了该问题。 -
若暂时无法重构继承关系?慎用
spy或callRealMethod:虽可行(如spy(new DataSource())并 stubgetConnection),但属于技术债方案,易使测试脆弱且掩盖设计缺陷。 -
测试焦点始终是行为,而非实现细节:验证
setup(...)是否正确委托并返回getConnection(...)的结果,而非关心anothermethod(props)是否被调用——除非它是DataSource的公开契约。
✅ 总结
继承增强了代码复用,但也提高了测试复杂度;而组合赋予测试灵活性与清晰边界。面对“子类调用父类受保护方法导致测试失败”的问题,根本解法不是深入继承树去 mock 每一层,而是重新审视设计——将依赖显式化、可替换、可验证。这不仅解决当前测试困境,更推动系统向高内聚、低耦合、易演化的方向演进。

















