Java单元测试Mock失败求助:myHostClient方法测试报错
问题描述
我尝试为myHostClient方法编写单元测试,但遇到了问题:
MyClass.java
import com.abc.def.ServiceBuilder; public class MyClass { @Value("${materialLocation}") private String materialValue; private static final ServiceBuilder SERVICEBUILDER = new ServiceBuilder(); @Bean public MyHostServiceClient myHostClient( @Value("${qualifier_one}") final String qualifierOne, @Value("${use_service}") final boolean useService) { if(useService) { return SERVICEBUILDER .remote(MyHostServiceClient.class) .withConfig(qualifierOne) .withCall(new CallerAttach(Caller.retro(defaultStrategy())), new SigningVisitor(new CredentialsProvider(materialValue)), call -> call.doSomeStuff(StuffObject.getStuffInstance())) .makeClient(); } // 补充原代码缺失的else分支逻辑 return null; } @Bean DefaultStrategy<Object> defaultStrategy() { final int valueA = 1; final int valueB = 2; return new DoSomeThingsBuilder() .retry(valueA) .doSomethingElse(valueB) .create(); } }
失败的测试代码(MyClassTest.java)
import org.mockito.Mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class MyClassTest { @Mock private ServiceBuilder serviceBuilder; private MyClass myClass; private String qualifierOne = "pass"; @BeforeEach void setUp() { myClass = new MyClass(); } @Test public void test_myHostClient() { boolean useService = true; final MyHostServiceClient result = myClass.myHostClient(qualifierOne, useService); // 错误行1 verify(serviceBuilder, times(1)); } }
错误信息
IllegalArgumentException: Material Name cannot be null
错误指向测试中的调用行,根源是代码中.withCall(new CallerAttach(Caller.retro(defaultStrategy())),这一行——materialValue未被注入,导致CredentialsProvider初始化失败。
解决方案
问题根源
materialValue未初始化:测试中直接实例化MyClass,Spring的@Value注解不会生效,materialValue为null,触发参数校验异常。- 静态常量Mock失效:
SERVICEBUILDER是静态final常量,Mockito无法直接Mock该静态成员,测试中定义的serviceBuilder和类内的静态实例完全无关。 - 验证语法错误:
verify(serviceBuilder, times(1))未指定要验证的方法调用,语法不完整。
修复后的测试代码
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; public class MyClassTest { private MyClass myClass; private String qualifierOne = "pass"; private String mockMaterialValue = "test-material-location"; @BeforeEach void setUp() { myClass = new MyClass(); // 用反射手动注入@Value标注的私有属性 ReflectionTestUtils.setField(myClass, "materialValue", mockMaterialValue); } @Test public void test_myHostClient_whenUseServiceIsTrue() { boolean useService = true; // Mock ServiceBuilder的静态实例及链式调用 try (MockedStatic<ServiceBuilder> mockedStaticBuilder = Mockito.mockStatic(ServiceBuilder.class)) { // 模拟链式调用的每个步骤 ServiceBuilder mockBuilder = mock(ServiceBuilder.class); when(mockBuilder.remote(eq(MyHostServiceClient.class))).thenReturn(mockBuilder); when(mockBuilder.withConfig(eq(qualifierOne))).thenReturn(mockBuilder); when(mockBuilder.withCall(any(CallerAttach.class), any(SigningVisitor.class), any())).thenReturn(mockBuilder); MyHostServiceClient mockClient = mock(MyHostServiceClient.class); when(mockBuilder.makeClient()).thenReturn(mockClient); // 指定静态构造方法返回mock实例 mockedStaticBuilder.when(ServiceBuilder::new).thenReturn(mockBuilder); // 调用测试方法 MyHostServiceClient result = myClass.myHostClient(qualifierOne, useService); // 验证链式调用的每个方法都被执行一次 verify(mockBuilder).remote(eq(MyHostServiceClient.class)); verify(mockBuilder).withConfig(eq(qualifierOne)); verify(mockBuilder).withCall(any(CallerAttach.class), any(SigningVisitor.class), any()); verify(mockBuilder).makeClient(); // 验证返回结果为mock的客户端实例 assert result == mockClient; } } @Test public void test_myHostClient_whenUseServiceIsFalse() { boolean useService = false; MyHostServiceClient result = myClass.myHostClient(qualifierOne, useService); // 根据原代码逻辑验证返回值 assert result == null; } }
关键修复点
- 注入私有属性:使用
ReflectionTestUtils手动设置materialValue,避免null导致的参数异常。 - Mock静态类:通过
MockedStatic静态Mock工具,解决静态final常量无法Mock的问题。 - 模拟链式调用:让
ServiceBuilder的每个链式方法都返回自身的mock实例,保证调用链能正常执行。 - 明确验证逻辑:指定要验证的具体方法,而非仅验证实例被调用。
额外建议
如果可以重构MyClass,建议将SERVICEBUILDER改为构造函数注入的依赖,而非静态常量,这样测试时无需Mock静态类,会更简洁易维护。
内容的提问来源于stack exchange,提问作者dredbound
相关产品推荐
相关产品推荐

