为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
相关产品推荐
相关产品推荐

