You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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初始化失败。

解决方案

问题根源

  1. materialValue未初始化:测试中直接实例化MyClass,Spring的@Value注解不会生效,materialValue为null,触发参数校验异常。
  2. 静态常量Mock失效:SERVICEBUILDER是静态final常量,Mockito无法直接Mock该静态成员,测试中定义的serviceBuilder和类内的静态实例完全无关。
  3. 验证语法错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 13:15:32