如何对使用转换器的 Grails 服务进行单元测试?

Posted

技术标签:

【中文标题】如何对使用转换器的 Grails 服务进行单元测试?【英文标题】:How do I unit test a Grails service that uses a converter? 【发布时间】:2012-07-31 21:51:59 【问题描述】:

我有一个 Grails 服务,它使用第三方服务通过 HTTP 调用发送电子邮件:

class EmailService 
    def sendEmail(values) 
        def valueJson = values as JSON
        ... // does HTTP call to 3rd party service
    

我已经编写了一个单元测试来测试这个服务(因为集成测试会启动 Hibernate 和整个域框架,我不需要):

@TestFor(EmailService)
class EmailServiceTests 
    void testEmailServiceWorks() 
        def values = [test: 'test', test2: 'test2']
        service.sendEmail(values)
    

但是,当我执行此单元测试时,它在尝试进行 as JSON 转换时失败并出现此异常:

org.apache.commons.lang.UnhandledException: org.codehaus.groovy.grails.web.converters.exceptions.ConverterException: Unconvertable Object of class: java.util.LinkedHashMap

然后我重新编写了我的单元测试以执行以下操作:

void testEmailServiceWorks() 
    def value = [test: 'test', test2: 'test2']
    def valueJson = value as JSON

当它尝试进行as JSON 转换时,我得到了同样的异常。

有谁知道我为什么会收到此异常,以及如何解决它?

【问题讨论】:

【参考方案1】:

即使您正在测试服务,您也可以将 @TestMixin(ControllerUnitTestMixin) 注释应用于您的测试类,以让 Grails 设置 JSON 转换器。

【讨论】:

这是解决这个问题的方法。如果我能不止一次投票,我会的。 太困惑了,我在 3 个地方看到了这个答案,并且确信它不会工作,因为我正在使用服务。与大多数 grails 一样,只是盲目地遵循惯例,它就像一种魅力。谢谢! 在一种情况下,我不小心使用了 \@Mixin 而不是 \@TestMixin。 HTH 别人。 这必须被选为最好的。 这适用于 Grails 2.3.4。如果您需要导入,请使用:import grails.test.mixin.TestMixinimport grails.test.mixin.web.ControllerUnitTestMixin【参考方案2】:

as JSON 魔法是在域框架启动时创建的。

您必须将测试更改为集成测试或模拟 asType。

def setUp()
    java.util.LinkedHashMap.metaClass.asType =  Class c ->
        new grails.converters."$c"(delegate)
    

请记住在拆解过程中自己进行清理,您不希望测试套件中出现元编程泄漏。

def tearDown()
    java.util.LinkedHashMap.metaClass.asType = null

编辑: 如果您来自未来,请考虑以下答案:https://***.com/a/15485593/194932

【讨论】:

这将如何在 grails 1.3.7 中工作。当我使用这个。我在意外令牌处收到运行时错误:。新 grails.converters."$c"(delegate) 很好的答案 - 让我朝着正确的方向前进。给定的 asType 语法不起作用,但 c.newInstance(delegate) 对我有用。 这不起作用,正确的解决方案是添加@TestMixin(ControllerUnitTestMixin) 注释(请查看下面@Stephen 的答案) 要清理,请尝试@ConfineMetaClassChanges(LinkedHashMap) spock api docs【参考方案3】:

由于 Grails 3.3.x grails-test-mixins 插件已被弃用。 @见migration guide。

对于这个问题,你应该实现来自Grails Testing Support Framework的GrailsWebUnitTest

【讨论】:

谢谢,这让我为 Grails 3.3.x 指明了正确的方向。但你可能有兴趣知道我最终找到了a more narrowly-scoped solution。【参考方案4】:

您可以在 setUp() 中初始化 JSON。有各种实现 ObjectMarshaller 的编组器,需要将它们添加到 ConverterConfiguration 才能进行 JSON 转换。

http://grails.github.io/grails-doc/2.4.4/api/index.html?org/codehaus/groovy/grails/web/converters/marshaller/json/package-summary.html

示例:

 DefaultConverterConfiguration<JSON> defaultConverterConfig = new  DefaultConverterConfiguration<JSON>()
 defaultConverterConfig.registerObjectMarshaller(new CollectionMarshaller())
 defaultConverterConfig.registerObjectMarshaller(new MapMarshaller())
 defaultConverterConfig.registerObjectMarshaller(new GenericJavaBeanMarshaller())

 ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(JSON.class, defaultConverterConfig);

【讨论】:

@Maladon 这是因为 Grails 基础架构从 Codehaus 迁移而来。试试这个:grails.github.io/grails-doc/2.4.4/api/index.html?org/codehaus/…【参考方案5】:

我刚刚遇到了这个问题,我真的不想按照此处另一个答案中的建议实施GrailsWebUnitTest。我想让我的服务测试尽可能“纯粹”和精简。我最终这样做了:

void setupSpec() 
    defineBeans(new ConvertersGrailsPlugin())


void cleanupSpec() 
    ConvertersConfigurationHolder.clear()

当您实现GrailsWebUnitTest(通过WebSetupSpecInterceptorWebCleanupSpecInterceptor)时,这就是它在后台发生的方式。


也就是说,转换器似乎是为在 Web 层中使用而设计的,主要是为了便于从控制器透明地返回不同格式的数据。值得考虑一下为什么您正在测试的服务首先需要转换器。

例如,在我的例子中,有人使用 JSON 转换器将一些数据序列化为字符串,以便将其存储在数据库的单个字段中。这似乎不是转换器的合适用户,所以我计划改变它的完成方式。在我的服务测试中提供转换器是一种临时解决方案,可以让我在重构之前提高测试覆盖率。

【讨论】:

【参考方案6】:

在尝试对调用“将 myMap 渲染为 JSON”的控制器进行单元测试时,我遇到了同样的错误。我们使用 Grails 1.3.7 并且没有其他解决方案对我有用而不引入其他问题。目前升级 Grails 不是我们的替代方案。

我的解决方案是使用 JSONBuilder 而不是“as JSON”,如下所示:

render(contentType: "application/json", myMap)

见http://docs.grails.org/latest/guide/theWebLayer.html#moreOnJSONBuilder

(我意识到这是旧的,但来到这里是为了寻找解决方案,其他人也可能如此)

【讨论】:

以上是关于如何对使用转换器的 Grails 服务进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Grails 2.0 服务中对 i18n 注入 messageSource 的使用进行单元或集成测试

grails Grails 单元测试中的应用程序访问

如何模拟闭包以测试 Grails 服务结果?

在 Grails 2.2 中是不是可以对 mongodb 动态属性进行单元测试?

Grails如何对异常执行单元测试

Grails Spock单元测试需要模拟事务管理器