samedi 30 juillet 2016

Untestable grails (2.5.4) service using @PostConstruct with Spock unit testing

I have a service that I wish to initialize with @PostConstuct, by fetching some configuration entries in Config.groovy.

I also wish to check that these entries were properly configured, and throw an exception so that I see that the application was misconfigured.

When writing a unit test for this service, I came to a dead end in Spock.

Spock apparently calls the @PostConstruct method, but only on the Shared Service instance, and then executes whatever instance methods you test, on the real instance under test.

This has a perverse side effect:

My init code either fails because I fail to add a setupSpec to initialize the shared instance, or it fails in the method under test, because the configuration has not actually been set on that instance.

Here's my service:

package issue

import org.codehaus.groovy.grails.commons.GrailsApplication

import javax.annotation.PostConstruct

class MyService {
    GrailsApplication grailsApplication
    String property

    @PostConstruct
    void init() {
        println "Initializing... ${this}"
        property = grailsApplication.config.myProperty

//Enabling this business sanity check make the service untestable under Spock, because to be able to run, we need to initialize the configuration
// of the shared instance - PostConstruct is only called on the shared instance for some reason.
// But the execution of the method under test will not have the initialized property, because the service being executed is not the shared instance
        if (property == "[:]") {
            throw new RuntimeException("This property cannot be empty")
        }
    }


    void doSomething() {
        println "Executing... ${this}"
        println(property.toLowerCase())
    }
}

Here's my first test:

package issue

import grails.test.mixin.TestFor
import spock.lang.Specification

@TestFor(MyService)
class MyServiceSpec extends Specification {

    def setup() {
        grailsApplication.config.myProperty = 'myValue'
    }

    void "It fails to initialize the service"() {
        expect:
        false // this is never executed
    }
}

Here's the second test:

package issue

import grails.test.mixin.TestFor
import spock.lang.Specification

@TestFor(MyService)
class MyServiceWithSharedInstanceInitializationSpec extends Specification {

    //Initializing the shared instance grailsApplication lets the @PostConstruct work, but will fail during method test
    //because the instance that was initialized is the shared instance
    def setupSpec() {
        grailsApplication.config.myProperty = 'myValue'
    }

    void "It fails to execute doSomething"() {
        when:
        service.doSomething()

        then:
        def e = thrown(NullPointerException)
        e.message == 'Cannot invoke method toLowerCase() on null object'
        service.property == null
    }
}

Is there a way to do this cleanly? Or do I have to let go my unit test and just make a (slower) integration test, to tiptoe around this weirdness?

You can see my full grails app here:

http://ift.tt/2ayO702

Aucun commentaire:

Enregistrer un commentaire