无需抽象类:解决Mock新建对象thenReturn失效与Bean实例化异常
解决直接new对象导致Mock失效且兼容SpringBoot端到端测试的方案
问题背景
- 初始实现:
BSending构造函数直接通过new RandomNumGen(thresh)创建对象,单元测试中无法MockRandomNumGen,导致预期的aSender.printLine()未触发 - 尝试方案:用抽象类
Conf封装对象创建逻辑,单元测试可MockConf的newRandomNumGen()方法返回Mock实例,但SpringBoot端到端测试因抽象类无法实例化,抛出BeanInstantiationException
无抽象类的可行解决方案
方案1:直接采用构造注入改造BSending
核心思路是把RandomNumGen作为构造参数传入BSending,而非在类内部直接new,从根源上消除无法Mock的问题。
改造后的BSending类
public class BSending { private final RandomNumGen randomNumGen; private final Sender aSender; // 构造注入RandomNumGen,不再内部创建实例 public BSending(int thresh, Sender aSender, RandomNumGen randomNumGen) { this.randomNumGen = randomNumGen; this.aSender = aSender; } // 业务方法示例 public void doSend() { int num = randomNumGen.generate(); if (num > 0) { aSender.printLine("Success"); } } }
单元测试(Mockito实现)
直接MockRandomNumGen实例,传入BSending构造即可完成测试:
@ExtendWith(MockitoExtension.class) public class BSendingTest { @Mock private Sender mockSender; @Mock private RandomNumGen mockRandomNumGen; @Test public void testDoSend() { // 配置Mock返回值 when(mockRandomNumGen.generate()).thenReturn(1); // 传入Mock实例创建BSending BSending bSending = new BSending(5, mockSender, mockRandomNumGen); bSending.doSend(); // 验证预期方法被触发 verify(mockSender).printLine("Success"); } }
端到端测试(SpringBootTest)
在Spring配置类中声明RandomNumGen和BSending的Bean,Spring会自动完成依赖注入,不会出现抽象类实例化问题:
@Configuration public class AppConfig { @Bean public RandomNumGen randomNumGen() { // 可根据实际需求从配置文件读取阈值,这里用固定值示例 return new RandomNumGen(10); } @Bean public BSending bSending(Sender sender, RandomNumGen randomNumGen) { return new BSending(10, sender, randomNumGen); } }
之后直接使用@SpringBootTest即可正常运行端到端测试。
方案2:用普通工厂类封装对象创建
如果无法直接修改BSending的构造参数,可使用非抽象的工厂类封装RandomNumGen的创建逻辑,既保留封装性,又能兼容Spring实例化。
普通工厂类与改造后的BSending
// 非抽象的工厂类 public class RandomNumGenFactory { public RandomNumGen create(int thresh) { return new RandomNumGen(thresh); } } // 改造后的BSending public class BSending { private final RandomNumGen randomNumGen; private final Sender aSender; public BSending(int thresh, Sender aSender, RandomNumGenFactory factory) { this.randomNumGen = factory.create(thresh); this.aSender = aSender; } // 业务方法同前 }
单元测试
Mock工厂类的create方法,让它返回Mock的RandomNumGen实例:
@ExtendWith(MockitoExtension.class) public class BSendingTest { @Mock private Sender mockSender; @Mock private RandomNumGen mockRandomNumGen; @Mock private RandomNumGenFactory mockFactory; @Test public void testDoSend() { when(mockFactory.create(anyInt())).thenReturn(mockRandomNumGen); when(mockRandomNumGen.generate()).thenReturn(1); BSending bSending = new BSending(5, mockSender, mockFactory); bSending.doSend(); verify(mockSender).printLine("Success"); } }
端到端测试
将工厂类声明为Spring Bean,Spring可正常实例化普通类,无异常:
@Configuration public class AppConfig { @Bean public RandomNumGenFactory randomNumGenFactory() { return new RandomNumGenFactory(); } @Bean public BSending bSending(Sender sender, RandomNumGenFactory factory) { return new BSending(10, sender, factory); } }
方案选择建议
- 优先选方案1:完全符合依赖注入原则,代码更简洁,测试维护成本更低
- 方案2适合:无法修改
BSending构造参数的遗留代码场景,保留了对象创建的封装逻辑
内容的提问来源于stack exchange,提问作者user5739619
相关产品推荐
相关产品推荐

