Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I unit test a Grails service that uses a converter?

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?

like image 338
Daniel T. Avatar asked Aug 02 '12 21:08

Daniel T.


5 Answers

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.

like image 72
Stephen Swensen Avatar answered Oct 23 '22 08:10

Stephen Swensen


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

like image 33
Raphael Avatar answered Oct 23 '22 07:10

Raphael


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.

like image 7
ayZagen Avatar answered Oct 23 '22 07:10

ayZagen


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);
like image 5
jonh Avatar answered Oct 23 '22 07:10

jonh


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.

like image 3
Doug Paul Avatar answered Oct 23 '22 08:10

Doug Paul