
本文讲解如何对继承自父类的子类进行单元测试,当其公有方法内部调用父类受保护方法时,应避免直接 mock 子类,而是通过构造注入+接口/抽象类模拟实现可控状态,从而隔离测试目标行为。
本文讲解如何对继承自父类的子类进行单元测试,当其公有方法内部调用父类受保护方法时,应避免直接 mock 子类,而是通过构造注入+接口/抽象类模拟实现可控状态,从而隔离测试目标行为。
在 Java 单元测试(尤其是使用 Mockito)中,一个常见误区是试图对被测类本身使用 @InjectMocks 或 Mockito.mock() —— 这不仅违背测试原则,还极易导致 IllegalStateException 等运行时异常(如您遇到的 "user not set")。根本原因在于:DataSource 的 setup() 方法依赖 ExtendedDataSource.getConnection(),而后者又依赖 AbstractSource.getUser(),该方法在未初始化上下文时必然失败。
✅ 正确做法:面向协作,而非继承
与其强行 mock 抽象类链(AbstractSource → AnotherAbstractClass),不如重构设计,将“继承关系”转为“组合关系”。这既提升可测性,也符合 SOLID 原则中的 依赖倒置(DIP) 和 里氏替换(LSP) 的实践建议:
// 重构后: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); // 直接委托
}
}
✅ 测试代码:精准控制依赖行为
此时测试变得清晰、轻量且可预测:
class DataSourceTest {
@Test
void setup_shouldReturnConnection_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(DataSource.class)或@InjectMocks DataSource—— 它是被测对象(SUT),不是依赖;- ✅ 所有
@Mock对象应是DataSource的协作对象(如ExtendedDataSource,Connection,Properties);- ✅ 若无法重构继承结构(如遗留系统),可考虑使用
PowerMockito模拟getUser(),但这是权宜之计,会增加测试脆弱性与维护成本;- ✅
Properties是 JDK 类,无需@Mock;直接new Properties()即可,除非需控制其行为(此时再 mock)。
✅ 总结
测试的核心是验证行为,而非绕过逻辑。当继承链导致测试困难时,优先审视设计:能否用组合替代继承?能否将受保护方法提取为策略接口?这些改进不仅能解决当前测试问题,更能提升代码的内聚性、可扩展性与长期可维护性。真正的“可测性”,从来都是良好架构的副产品。










