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

为Controller编写测试时如何在Service中注入属性值

问题:Micronaut Service添加@Value属性后测试用例执行失败

我正在开发一个Micronaut应用,在MyService类中通过@Value注解注入配置属性${scheduler.jobtime},Service类代码如下:

@Context
@AllArgsConstructor
@Slf4j
public class MyService {
    private final ExternalApiClient externalApiClient;
    private final OtherService1 otherService1;
    

    @Value("${scheduler.jobtime}")
    private final String cronExpr;

    public Job createJob() {
       
       // 使用externalApiClient的方法实现
    }

    public boolean haveStartedToday() {
        return otherService1.somemethod(cronExpr);
    }
}

添加该属性后,Controller的集成测试用例执行失败,测试代码如下:

@MicronautTest(transactional = false)
@Tag("integration")
class ControllerTest {
    private static final UUID ID = UUID.randomUUID();
    @Inject
    @Client("/")
    HttpClient client;

    @Inject
    MyService myService;

    @MockBean(MyService.class)
    @Context
    MyService myService() {
        final MyService mock = mock(MyService.class);
        when(mock.createJob()).thenReturn(Job.builder()
                .status("CREATED")
                .fullLoad(true)
                .build());
        return mock;
    }

    @Test
    void executeJob() throws JSONException {
        HttpResponse<String> response = client.toBlocking()
            .exchange(HttpRequest.POST("/job", ""), String.class);

        verify(myService).createJob();

        assertThat(response.code()).isEqualTo(HttpStatus.OK.getCode());
        Map<String, Object> hashMap = Map.of("id", ID, "status", "CREATED");
        JSONAssert.assertEquals(response.body(), new JSONObject(hashMap), 
        JSONCompareMode.LENIENT);
    }
}

报错信息:

Bean definition [com.endpoints.job.$ControllerTest$MyService0Definition$Intercepted] could not be loaded: Error instantiating bean of type  [com.endpoints.job.$ControllerTest$MyService0Definition$Intercepted]

原因分析

问题出在@AllArgsConstructor与@Value的组合使用上:

  • @AllArgsConstructor会生成包含所有final字段的构造函数,而@Value标注的cronExpr是final字段,Micronaut在创建MyService的mock代理时,会尝试通过构造函数注入所有依赖,但测试环境未提供cronExpr的配置值,也未在mock构造时处理该参数,导致实例化失败。
  • 测试中用@MockBean直接mockMyService,但@Context注解会让Micronaut尝试基于原类的构造逻辑创建代理类,原类构造函数现在需要cronExpr参数,最终引发报错。

解决方法

方案一:统一构造函数注入(推荐)

将@Value注入的字段移到构造函数中,让依赖注入逻辑更清晰,同时兼容Lombok的注解:

@Context
@Slf4j
public class MyService {
    private final ExternalApiClient externalApiClient;
    private final OtherService1 otherService1;
    private final String cronExpr;

    // 显式构造函数,Micronaut会自动解析@Value对应的配置属性
    public MyService(ExternalApiClient externalApiClient, OtherService1 otherService1,
                     @Value("${scheduler.jobtime}") String cronExpr) {
        this.externalApiClient = externalApiClient;
        this.otherService1 = otherService1;
        this.cronExpr = cronExpr;
    }

    // 业务方法保持不变
    public Job createJob() {
       // 使用externalApiClient的方法实现
    }

    public boolean haveStartedToday() {
        return otherService1.somemethod(cronExpr);
    }
}

也可以替换@AllArgsConstructor为@RequiredArgsConstructor,Lombok会自动生成包含所有final字段的构造函数,Micronaut能识别构造函数中的@Value注解。

方案二:在测试中提供配置属性

无需修改Service代码,直接在测试类上添加@Property注解提供测试用的配置值:

@MicronautTest(transactional = false)
@Tag("integration")
@Property(name = "scheduler.jobtime", value = "0 0 12 * * ?") // 添加配置
class ControllerTest {
    // 测试代码保持不变
}

这样Micronaut在创建mock代理时能获取到配置值,完成bean实例化。

方案三:简化MockBean实现

既然已经用Mockito创建了MyService的mock,无需让Micronaut做额外的上下文bean处理,去掉@Context注解即可:

@MockBean(MyService.class)
MyService myService() {
    final MyService mock = mock(MyService.class);
    when(mock.createJob()).thenReturn(Job.builder()
            .status("CREATED")
            .fullLoad(true)
            .build());
    // 如果测试中用到haveStartedToday方法,需要补充mock逻辑
    when(mock.haveStartedToday()).thenReturn(true);
    return mock;
}

内容的提问来源于stack exchange,提问作者Manish

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 23:03:07