loteq loteq - 4 months ago 23
Groovy Question

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:

https://github.com/LuisMuniz/grails-spock-issue-with-postconstruct

Answer

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.

My advice is to simply call the init method, since you are testing the logic and functionality of the method and not whether or not @PostConstruct works, this seems to make most sense.