将 Grails 应用程序从 5.3.2 升级到 6.0.0 后,我在集成测试中解决 i18n 消息时遇到问题。我在 110 个集成测试中的 26 个中遇到了类似的错误:
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() {
// omitting some domain classes that are created here
}
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() {
// Use the ServiceAccount created in Bootstrap.groovy
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: "we call the method being tested"
HttpRequest request = HttpRequest.GET(uri)
HttpResponse<String> resp = this.client.toBlocking().exchange(request, String)
Map json = new JsonSlurper().parseText(resp.body()) as Map
then: "we get a success status"
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: "the response has the correct dateformat for the locale of the request"
json.itemDisplayDateTimeFormat == format
when: "we call the method again with a bad 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: "we get a 400 status"
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 项目,但尚未能够重现该问题,因此我们的应用程序配置很可能出现问题,从而导致 NoSuchMessageExceptions。
在发生问题的应用程序中,我注意到非英语区域设置的控制器集成测试用例已通过。在调用我的控制器方法时,我通过在 URL 中设置
lang
参数来指定区域设置,即 lang=es
。
当我添加与我的
messages_en.properties
文件内容相同的 messages.properties
文件时,我所有的集成测试都通过了。所以现在,我的解决方法是创建一个到 messages.properties 的符号链接并将该文件提交到我们的存储库:
cd grails-app/i18n
ln -s messages.properties messages_en.properties