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

32

我有一个 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)
    }
}

然而,当我执行这个单元测试时,它在尝试进行 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转换时,我遇到了相同的异常。

有谁知道为什么会出现这种异常,以及我该如何解决它?

7个回答

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

这是解决问题的正确方法。如果我可以投票超过一次,我一定会这样做。 - Rob Fletcher
1
太令人困惑了,我在三个地方看到了这个答案,但是我确信它不会起作用,因为我正在使用服务。就像大多数Grails一样,只需盲目地遵循传统约定,它就能像魔法般奏效。谢谢! - Jackie
有一次,我不小心使用了 @Mixin 而不是 @TestMixin。希望对其他人有所帮助。 - RMorrisey
这必须被选为最佳的一个。 - Vadim
那对于Grails 2.3.4有效。如果你需要导入,请使用:import grails.test.mixin.TestMixinimport grails.test.mixin.web.ControllerUnitTestMixin - nbkhope
此答案不适用于Grails 3.3.x。 - ayZagen

10

当领域框架启动时,就会创建出“as JSON”的魔法。

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

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

在tearDown方法中记得清理干净,否则你不会想要测试套件中出现元编程泄漏。

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

编辑: 如果您来自未来,请考虑此答案:https://dev59.com/vWgt5IYBdhLWcg3w0wzB#15485593


这在grails 1.3.7中会如何工作?当我使用它时,我会得到一个运行时错误,提示意外的标记:. new grails.converters."${c}"(delegate)。 - allthenutsandbolts
6
好的回答 - 让我朝正确的方向前进。给出的asType语法不起作用,但是c.newInstance(delegate)对我有效。 - Armand
2
这个不起作用,正确的解决方案是添加@TestMixin(ControllerUnitTestMixin)注释(请查看下面@Stephen的答案)。 - mathifonseca
1
为了清理,请尝试使用 @ConfineMetaClassChanges(LinkedHashMap) spock api docs - Ryan Heathcote

7

2
感谢您为Grails 3.3.x指明了正确的方向。但您可能会感兴趣的是,我最终找到了一个更具针对性的解决方案。链接 - Doug Paul

6

我刚遇到这个问题,真的不想像这里的另一个答案建议那样实现GrailsWebUnitTest。我希望将我的服务测试保持“纯净”和简洁。最终我采用了以下方法:

void setupSpec() {
    defineBeans(new ConvertersGrailsPlugin())
}

void cleanupSpec() {
    ConvertersConfigurationHolder.clear()
}

当你使用GrailsWebUnitTest(通过WebSetupSpecInterceptorWebCleanupSpecInterceptor)实现时,它是如何在幕后发生的。


话虽如此,这些转换器似乎是用于Web层的,主要是为了使从控制器中透明地返回不同格式的数据变得容易。有必要考虑一下你正在测试的服务为什么需要这些转换器。

例如,在我的情况下,有人使用JSON转换器将某些数据序列化成字符串,以便将其存储在数据库的单个字段中。这似乎不是转换器的适当用法,因此我打算更改它的实现方式。在我的服务测试中使用这些转换器是一个临时解决方案,允许我在重构代码之前提高我们的测试覆盖率。


5
你可以在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);

1
@Maladon 这是因为Grails基础设施从Codehaus迁移。请尝试使用此链接:http://grails.github.io/grails-doc/2.4.4/api/index.html?org/codehaus/groovy/grails/web/converters/marshaller/json/package-summary.html - BahmanM

1

当我尝试对调用"render myMap as JSON"的控制器进行单元测试时,我遇到了相同的错误。我们使用的是Grails 1.3.7,没有其他解决方案适用于我而不引入其他问题。目前升级Grails不是我们的选择。

我的解决方案是使用JSONBuilder代替"as JSON",就像这样:

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

请查看http://docs.grails.org/latest/guide/theWebLayer.html#moreOnJSONBuilder

(我知道这个问题有点老了,但是我在寻找解决方案时来到这里,可能其他人也会这样)


0
在较新的Grails版本中,支持测试traits,只需实现GrailsWebUnitTest即可。
import spock.lang.*
import grails.testing.web.GrailsWebUnitTest
import grails.testing.services.ServiceUnitTest

class MyServiceSpec extends Specification implements ServiceUnitTest<MyService>, GrailsWebUnitTest {
...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接