Grails 6集成测试因找不到i18n消息失败,如何解决?
Grails 5.3.2升级至6.0.0后集成测试i18n消息解析异常问题
升级Grails应用从5.3.2到6.0.0后,我的110个集成测试中有26个出现i18n消息解析失败的问题,典型错误如下:
org.springframework.context.NoSuchMessageException: No message found under code 'itemDisplayDateTimeFormat' for locale 'en'. at org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:161) at com.hypercision.attproto.device.DeviceManagementController.getDataForDeviceApproval(DeviceManagementController.groovy:93) at com.hypercision.attproto.device.DeviceManagementController.getDataForDeviceApproval(DeviceManagementController.groovy) at org.grails.core.DefaultGrailsControllerClass$MethodHandleInvoker.invoke(DefaultGrailsControllerClass.java:223)
该问题在本地机器和CircleCI工作流中均会出现,我尝试删除项目根目录下的.gradle目录,但并未解决错误。
已解决的服务集成测试处理方式
我通过手动创建并注入StaticMessageSource(和单元测试中的做法一致),让一个服务集成测试恢复正常,代码如下:
@Integration @Rollback class SubmissionServiceIntegrationSpec extends Specification { @Autowired SubmissionService submissionService StaticMessageSource staticMessageSource = new StaticMessageSource() def setup() { staticMessageSource.addMessage("SubmissionService.submit.sessionAlreadySubmittedError", Locale.US, "session already submitted") submissionService.setMessageSource(staticMessageSource) } def setupData() { // 此处省略创建领域类的代码 } def "test submitAllSessionsInGroup when the SessionItem is in a group of two and there are errors"() { given: "the SessionItem is in a SessionItemGroup that has 2 sessionItems" setupData() assert sessionItemGroup.getSize() == 2 and: "One of the sessionItems is submitted" sessionItem1.status = "Submitted" assert sessionItem2.status == "Not Started" assert sessionItem2.submitTS == null when: submissionService.submitAllSessionsInGroup(sessionItem1) then: InvalidSessionItemStatusException exception = thrown() exception != null } }
待解决的控制器集成测试问题
但我不清楚如何对如下控制器集成测试执行同样的操作:
// src/integration-test/groovy/com/hypercision/attproto/device/DeviceManagementControllerIntegrationSpec.groovy package com.hypercision.attproto.device import com.hypercision.attproto.instructor.Instructor import com.hypercision.attproto.serviceaccount.ServiceAccount import grails.gorm.transactions.Rollback import grails.testing.mixin.integration.Integration import groovy.json.JsonSlurper import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse import io.micronaut.http.HttpStatus import io.micronaut.http.client.HttpClient import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.http.uri.UriBuilder import spock.lang.Shared import spock.lang.Specification import spock.lang.Unroll @Integration @Rollback class DeviceManagementControllerIntegrationSpec extends Specification { @Shared HttpClient client @Shared String accessToken @Shared Instructor instructor @Shared List<Device> devices = [] void setup() { // 使用Bootstrap.groovy中创建的ServiceAccount ServiceAccount serviceAccount = ServiceAccount.first() assert serviceAccount instructor = new Instructor(userID: UUID.randomUUID().toString(), fullName: "Wendy", serviceAccount: serviceAccount).save(failOnError: true) Device device1 = new Device(deviceID: UUID.randomUUID().toString(), registrationCode: "111", platform: "iOS", platformVersion: "12.1.0", instructor: instructor).save(failOnError: true) Device device2 = new Device(deviceID: UUID.randomUUID().toString(), registrationCode: "222", platform: "Android", platformVersion: "9.0.0", instructor: instructor, approvalToken: "aaabbb123").save(failOnError: true) devices = [device1, device2] } void cleanup() { Device.deleteAll(Device.getAll()) instructor.delete() } @Unroll void "test getDataForDeviceApproval returns 200"() { given: assert Device.count == 2 final String uriPath = '/deviceManagement/getDataForDeviceApproval' String uri = UriBuilder.of(uriPath) .queryParam("token", devices[1].approvalToken) .queryParam("lang", lang) .build() when: "调用被测试的方法" HttpRequest request = HttpRequest.GET(uri) HttpResponse<String> resp = this.client.toBlocking().exchange(request, String) Map json = new JsonSlurper().parseText(resp.body()) as Map then: "返回成功状态" resp.status == HttpStatus.OK json != null json.id == devices[1].id json.deviceID == devices[1].deviceID json.platform == devices[1].platform json.approved == devices[1].approved and: "响应包含对应请求locale的正确日期格式" json.itemDisplayDateTimeFormat == format when: "使用错误的approval token再次调用方法" uri = UriBuilder.of(uriPath) .queryParam("token", "this_does_not_match_any_saved_devices") .queryParam("lang", lang) .build() request = HttpRequest.GET(uri) this.client.toBlocking().exchange(request, String) then: "返回400状态" HttpClientResponseException ex = thrown() HttpResponse<String> exResponse = ex.response as HttpResponse<String> Map exJson = new JsonSlurper().parseText(exResponse.body()) as Map exResponse.status == HttpStatus.BAD_REQUEST and: exJson != null exJson.errors != null exJson.errors.size() == 1 exJson.errors[0].message == "Invalid token: device not found with that token." where: lang | format "en" | "MM/dd/yyyy HH:mm z" "en_GB" | "d MMM yyyy HH:mm z" } }
更新
我创建了一个Grails 6.0.0示例项目,但并未重现该问题,因此很可能是我们应用自身的配置问题导致了NoSuchMessageException。
内容的提问来源于stack exchange,提问作者wubbalubba
相关产品推荐
相关产品推荐

