32

I have a Grails service that sends out e-mails using a 3rd-party service by doing a HTTP call:

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

I've written a unit test to test this service (because an integration test spins up Hibernate and the entire domain framework, which I don't need):

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

However, when I execute this unit test, it fails with this exception when it tries to do the as JSON conversion:

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

I then re-wrote my unit test to just do the following:

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

And I get the same exception when it tries to do the as JSON conversion.

Does anyone know why I'm getting this exception, and how I can fix it?

Daniel T.
  • 37,212
  • 36
  • 139
  • 206

7 Answers7

72

Even though you are testing a service, you can apply the @TestMixin(ControllerUnitTestMixin) annotation to your test class to get Grails to set up the JSON converter.

Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • This is the way to go to solve this. If I could vote this up more than once I would. – Rob Fletcher Jan 29 '14 at 20:41
  • 1
    So confusing I saw this answer 3 places and was sure it wouldn't work since I was using a service. As with most of grails just blindly follow convention and it works like a charm. Thanks! – Jackie Jul 29 '14 at 18:47
  • In one case, I accidentally used \@Mixin instead of \@TestMixin. HTH someone else. – RMorrisey Jun 23 '16 at 15:41
  • This has to be selected as the best one. – Vadim Feb 20 '17 at 07:47
  • That worked for Grails 2.3.4. In case you need the imports, use: `import grails.test.mixin.TestMixin` and `import grails.test.mixin.web.ControllerUnitTestMixin` – nbkhope Jun 12 '17 at 22:18
  • this answer is not valid for grails 3.3.x – ayZagen Jul 12 '18 at 19:32
10

The as JSON magic is created when the domain framework spins up.

You have to either change your test to an integration one or mock the asType.

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

Rember to clean up after yourself in the tearDown, you wouldn't want metaprogramming leaks in your test suite.

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

Edit: If you come from the future, consider this answer: https://stackoverflow.com/a/15485593/194932

Community
  • 1
  • 1
Raphael
  • 1,760
  • 1
  • 12
  • 21
  • How will this work in grails 1.3.7. When I use this. I get an run time error at unexpected token: . new grails.converters."${c}"(delegate) – allthenutsandbolts Aug 09 '12 at 01:24
  • 6
    Great answer - got me going in the right direction. Given syntax for asType did not work, but `c.newInstance(delegate)` did for me. – Armand Sep 25 '12 at 09:58
  • 2
    this does not work, the correct solution is adding `@TestMixin(ControllerUnitTestMixin)` annotation (check @Stephen's answer below) – mathifonseca Jun 09 '16 at 14:25
  • 1
    For cleanup, try `@ConfineMetaClassChanges(LinkedHashMap)` [spock api docs](http://spockframework.github.io/spock/javadoc/1.0/spock/util/mop/ConfineMetaClassChanges.html) – Ryan Heathcote Jun 16 '16 at 20:33
7

As Grails 3.3.x grails-test-mixins plugin is deprecated. @see migration guide.

For this problem you should implement GrailsWebUnitTest which is coming from Grails Testing Support Framework.

ayZagen
  • 487
  • 7
  • 15
  • 2
    Thanks, this got me pointed in the right direction for Grails 3.3.x. But you might be interested to know I ended up finding [a more narrowly-scoped solution](https://stackoverflow.com/a/60889937/226829). – Doug Paul Mar 27 '20 at 16:25
6

I just ran into this, and I really didn't want to implement GrailsWebUnitTest as recommended in another answer here. I want to keep my service test as "pure" and lean as possible. I ended up doing this:

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

void cleanupSpec() {
    ConvertersConfigurationHolder.clear()
}

This is how it happens under the hood when you implement GrailsWebUnitTest (via WebSetupSpecInterceptor and WebCleanupSpecInterceptor).


That said, the converters seem to be meant for use in the web tier, primarily for making it easy to transparently return data in different formats from a controller. It's worth considering why the service you're testing needs the converters in the first place.

For example, in my case, someone used the JSON converter to serialize some data to a string so it could be stored in a single field in the database. That doesn't seem like an appropriate user of the converters, so I plan on changing how it's done. Making the converters available in my service test is a temporary solution to allow me to improve our test coverage before I refactor things.

Doug Paul
  • 1,221
  • 12
  • 16
5

you can initialise the JSON in the setUp() . There are various marshallers which implement ObjectMarshaller , which need to be added to the ConverterConfiguration for JSON conversion to work.

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

example :

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

 ConvertersConfigurationHolder.setTheadLocalConverterConfiguration(JSON.class, defaultConverterConfig);
peterp
  • 3,101
  • 3
  • 22
  • 37
jonh
  • 59
  • 1
  • 1
    @Maladon That's because of Grails infrastructure move from Codehaus. Try this: http://grails.github.io/grails-doc/2.4.4/api/index.html?org/codehaus/groovy/grails/web/converters/marshaller/json/package-summary.html – BahmanM Mar 01 '15 at 14:30
1

I was getting the same error when trying to unit test a controller that calls "render myMap as JSON". We use Grails 1.3.7 and none of the other solutions worked for me without introducing other problems. Upgrading Grails was not an alternative for us at the moment.

My solution was to use JSONBuilder instead of "as JSON", like this:

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

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

(I realize this is old, but came here in search for a solution and so might others)

0

On newer Grails, that supports testing traits, is enough to implement GrailsWebUnitTest.

import spock.lang.*
import grails.testing.web.GrailsWebUnitTest
import grails.testing.services.ServiceUnitTest

class MyServiceSpec extends Specification implements ServiceUnitTest<MyService>, GrailsWebUnitTest {
...
}
rodvlopes
  • 895
  • 1
  • 8
  • 18