Grails中Service测试时如何Mock Domain的get静态方法?
解决UnitMeasure静态get方法Mock失败导致NPE的思路
踩过不少静态方法Mock的坑,给你梳理几个靠谱的解决方案,帮你搞定这个空指针问题:
方案一:用Mockito 3.4+的原生静态Mock支持
现在Mockito已经原生支持静态方法Mock了,不用再依赖PowerMock这种重量级工具,步骤很清晰:
- 确保依赖正确:你的项目里需要引入
mockito-core和mockito-inline(静态Mock必须要这个依赖),比如Maven依赖:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.x.x</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.x.x</version> <scope>test</scope> </dependency>
- 编写测试代码:用
try-with-resources语法包裹静态Mock的逻辑,确保测试后自动清理Mock状态,避免影响其他测试:
import org.mockito.MockedStatic; import static org.mockito.Mockito.*; // 测试类上记得加Mockito扩展支持 @ExtendWith(MockitoExtension.class) class ValidationServiceTest { @Test void testValidation() { // 1. Mock UnitMeasure的静态类 try (MockedStatic<UnitMeasure> mockedUnitMeasure = mockStatic(UnitMeasure.class)) { // 2. 指定静态get方法的返回值,注意参数匹配要准确 UnitMeasure mockUm = new UnitMeasure("KG", "千克"); mockedUnitMeasure.when(() -> UnitMeasure.get(eq("KG"))) .thenReturn(mockUm); // 3. 执行你的ValidationService测试逻辑 ValidationService service = new ValidationService(); service.validate(new Request("KG")); // 4. 可选:验证静态方法是否被正确调用 mockedUnitMeasure.verify(() -> UnitMeasure.get(eq("KG"))); } } }
排查Mock失败的常见坑
- 是不是参数匹配不对?比如你用了
anyString()但实际调用时传的是特定字符串,或者反过来,导致Mock没触发,返回null - 是不是没加
mockito-inline依赖?这个是静态Mock的关键,少了它Mockito会直接报错 - 是不是测试类没加
@ExtendWith(MockitoExtension.class)(JUnit5)或者@RunWith(MockitoJUnitRunner.class)(JUnit4)?
方案二:重构代码,解耦静态方法依赖
如果静态Mock总是出问题,更推荐的做法是封装静态方法为可注入的依赖,从根源上避免静态方法带来的测试麻烦:
- 封装静态调用:创建一个接口来包装UnitMeasure的静态get方法:
public interface UnitMeasureProvider { UnitMeasure get(String unitCode); } public class DefaultUnitMeasureProvider implements UnitMeasureProvider { @Override public UnitMeasure get(String unitCode) { // 这里调用原来的静态方法 return UnitMeasure.get(unitCode); } }
- 修改ValidationService依赖:把原来直接调用静态方法的逻辑,改成注入这个Provider:
public class ValidationService { private final UnitMeasureProvider unitMeasureProvider; // 构造注入,方便测试时替换Mock对象 public ValidationService(UnitMeasureProvider unitMeasureProvider) { this.unitMeasureProvider = unitMeasureProvider; } public void validate(Request req) { // 现在调用Provider的方法,而不是直接调用静态方法 UnitMeasure um = unitMeasureProvider.get(req.getUnitCode()); // ... 后续验证逻辑 } }
- 编写测试:现在就可以像Mock普通对象一样Mock这个Provider了,完全不用管静态方法:
@ExtendWith(MockitoExtension.class) class ValidationServiceTest { @Mock private UnitMeasureProvider mockProvider; @InjectMocks private ValidationService validationService; @Test void testValidation() { // 指定Provider的返回值 UnitMeasure mockUm = new UnitMeasure("KG", "千克"); when(mockProvider.get(eq("KG"))).thenReturn(mockUm); // 执行测试 validationService.validate(new Request("KG")); // 验证调用 verify(mockProvider).get(eq("KG")); } }
这个方案不仅解决了测试问题,还让代码的耦合度更低,符合依赖倒置原则,后续维护也更方便。
方案三:旧项目用PowerMock(不推荐)
如果你的项目还在使用JUnit4,且无法升级Mockito版本,可以用PowerMock来Mock静态方法,但要注意PowerMock会修改类加载器,可能带来一些兼容性问题:
- 引入PowerMock依赖:Maven示例:
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
- 编写测试:
import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(UnitMeasure.class) // 告诉PowerMock要处理这个类 class ValidationServiceTest { @Test void testValidation() { // Mock静态类 PowerMockito.mockStatic(UnitMeasure.class); UnitMeasure mockUm = new UnitMeasure("KG", "千克"); PowerMockito.when(UnitMeasure.get(eq("KG"))).thenReturn(mockUm); // 执行测试逻辑 ValidationService service = new ValidationService(); service.validate(new Request("KG")); // 验证调用 PowerMockito.verifyStatic(UnitMeasure.class); UnitMeasure.get(eq("KG")); } }
总结
优先推荐方案二(重构解耦),这是长期来看最健康的做法;如果不想改代码,就用方案一的Mockito原生静态Mock;只有在旧项目无法升级时才考虑方案三的PowerMock。
内容的提问来源于stack exchange,提问作者Mister B
相关产品推荐
相关产品推荐

