如何使用JUnit与Mockito Mock被BeanDefinitions类调用的CircuitBreakerCustomizers静态方法
问题描述
我正在为下面的Java方法编写测试用例:
class BeanDefinitions { public Customizer<ReactiveResilience4JCircuitBreakerFactory> apiEnrichmentLayerCircuitBreakerCustomizer( final HttpClientConfigurationProperties clientsConfig ) { return CircuitBreakerCustomizers.fromHttpClientConfig( API_ENRICHMENT_LAYER_CLIENT_ID, clientsConfig.getHttpClients().get(API_ENRICHMENT_LAYER_CLIENT_ID) ); } }
我希望Mock类中调用的静态方法CircuitBreakerCustomizers.fromHttpClientConfig(不想执行该静态方法的实际逻辑,而是让它返回我指定的内容)。但当我执行断言Assertions.assertNotNull(beanDefinitions.apiEnrichmentLayerCircuitBreakerCustomizer(clientsConfig));时,总是遇到空指针异常(NPE),原因是该静态方法会执行一些我不想触发的操作。请问是否可以通过Mockito实现这一需求?
解决方案
当然可以!Mockito从3.4.0版本开始正式支持静态方法的Mocking,完全能满足你的需求。下面是具体的实现步骤和代码示例:
1. 确保依赖版本正确
首先要保证你的项目中引入了Mockito Core和Mockito Inline依赖,版本至少为3.4.0+(Inline依赖是实现静态Mock的关键)。如果用Maven的话,依赖配置大概是这样:
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <!-- 建议用较新的稳定版 --> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
2. 编写测试代码的核心逻辑
你需要使用mockStatic方法来创建静态类的Mock作用域,并且用try-with-resources语法来自动清理Mock(避免影响其他测试用例)。同时,别忘了先MockHttpClientConfigurationProperties相关的对象,确保clientsConfig.getHttpClients().get(API_ENRICHMENT_LAYER_CLIENT_ID)不会返回null(否则即使静态方法被Mock,参数传null也可能引发问题)。
完整的测试示例如下:
import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; class BeanDefinitionsTest { private static final String API_ENRICHMENT_LAYER_CLIENT_ID = "test-client-id"; @Test void apiEnrichmentLayerCircuitBreakerCustomizer_ShouldReturnCustomizer_WhenStaticMethodIsMocked() { // 1. Mock HttpClientConfigurationProperties及其内部的HttpClients Map HttpClientConfigurationProperties clientsConfig = mock(HttpClientConfigurationProperties.class); HttpClientProperties clientProps = mock(HttpClientProperties.class); // 假设这是getHttpClients返回的类型 when(clientsConfig.getHttpClients()).thenReturn(Map.of(API_ENRICHMENT_LAYER_CLIENT_ID, clientProps)); // 2. 创建你想要静态方法返回的自定义Customizer实例 Customizer<ReactiveResilience4JCircuitBreakerFactory> mockCustomizer = factory -> { // 这里可以留空,或者添加你需要的模拟逻辑 }; // 3. Mock静态类CircuitBreakerCustomizers,指定方法调用时返回mockCustomizer try (MockedStatic<CircuitBreakerCustomizers> mockedStatic = mockStatic(CircuitBreakerCustomizers.class)) { mockedStatic.when(() -> CircuitBreakerCustomizers.fromHttpClientConfig( API_ENRICHMENT_LAYER_CLIENT_ID, clientProps)) .thenReturn(mockCustomizer); // 4. 调用被测方法并断言结果 BeanDefinitions beanDefinitions = new BeanDefinitions(); Customizer<ReactiveResilience4JCircuitBreakerFactory> result = beanDefinitions.apiEnrichmentLayerCircuitBreakerCustomizer(clientsConfig); assertNotNull(result); assertSame(mockCustomizer, result); // 可选:验证返回的是你指定的实例 // 可选:验证静态方法是否被正确调用了一次 mockedStatic.verify(() -> CircuitBreakerCustomizers.fromHttpClientConfig( API_ENRICHMENT_LAYER_CLIENT_ID, clientProps), times(1)); } // try-with-resources会自动关闭mockedStatic,清理静态Mock } }
关键注意事项
- 静态Mock的作用域:
mockStatic创建的Mock是全局的,所以一定要用try-with-resources来包裹,确保测试结束后清理Mock,防止影响其他测试用例的执行。 - 参数匹配:在
when和verify中,要确保静态方法的参数匹配准确。如果不想严格匹配参数,可以用any()等匹配器,但建议尽量精准,保证测试的可靠性。 - 前置Mock的完整性:一定要确保
clientsConfig.getHttpClients().get(...)不会返回null,否则即使静态方法被Mock,传入null参数可能还是会引发意外的异常(取决于静态方法的实现,但提前Mock好可以避免这类问题)。
内容的提问来源于stack exchange,提问作者fatherazrael

