
本文讲解如何对继承自父类的子类进行单元测试,当其公共/受保护方法内部调用父类受保护方法时,避免因真实依赖(如未初始化的 user)导致 IllegalStateException;核心方案是重构为组合模式并合理使用 Mockito 模拟,而非尝试 mock 抽象类或破坏封装。
本文讲解如何对继承自父类的子类进行单元测试,当其公共/受保护方法内部调用父类受保护方法时,避免因真实依赖(如未初始化的 `user`)导致 `illegalstateexception`;核心方案是重构为组合模式并合理使用 mockito 模拟,而非尝试 mock 抽象类或破坏封装。
在 Java 单元测试中,面对 DataSource extends ExtendedDataSource 这类继承结构,直接实例化子类并调用 getConnection() 往往会触发父类链中未初始化的状态(例如 AbstractSource.getUser() 返回 null 导致 IllegalStateException),而试图通过 @Mock 注解 mock 抽象类或父类通常是无效且反模式的——Mockito 无法真正 mock final、static、私有方法,也不推荐 mock 被测类自身或其直接父类。
✅ 正确做法是:将继承关系重构为组合(Composition over Inheritance),让 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); // 委托调用
}
}✅ 对应的可测单元测试(使用 Mockito)
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class DataSourceTest {
@Test
void setup_shouldDelegateToExtendedDataSourceAndReturnConnection() {
// Given
Properties props = new Properties();
Connection mockConnection = mock(Connection.class);
ExtendedDataSource mockExtendedDS = mock(ExtendedDataSource.class);
when(mockExtendedDS.getConnection(props)).thenReturn(mockConnection);
DataSource dataSource = new DataSource(mockExtendedDS); // 注入模拟依赖
// When
Connection result = dataSource.setup(props);
// Then
assertNotNull(result);
assertSame(mockConnection, result);
verify(mockExtendedDS).getConnection(props);
verifyNoMoreInteractions(mockExtendedDS);
}
}⚠️ 注意事项与补充建议
-
不要 mock 抽象类或父类本身:
@Mock一个AbstractSource并不能解决getUser()的状态问题,且违背测试边界——你是在测DataSource的行为逻辑,不是测AbstractSource的实现。 -
避免
@InjectMocks在继承场景滥用:@InjectMocks适用于字段注入,但在存在继承+抽象方法时极易失效;显式构造更可控、语义更清晰。 -
若无法重构(遗留系统限制)? 可考虑:
- 使用
PowerMockito(不推荐,增加维护成本); - 在测试前通过反射设置
user字段(临时 workaround,仅限紧急场景); - 将
getUser()提取为可 mock 的策略接口(如UserProvider),实现依赖抽象化。
- 使用
-
始终验证交互(verify):确保
DataSource确实委托了调用,而非自行实现逻辑,这是组合模式下契约测试的关键。
通过组合重构 + 显式依赖注入,你的测试将真正聚焦于 DataSource 的职责——协调与委托,而非陷入父类状态初始化的泥潭。这既是测试友好之道,也是面向对象设计演进的自然选择。

















