Search code examples
grailstestingintegration-testinggrails-plugin

Are Grails Plugin service integration tests with GORM supposed to work?


Update

@Ramsharan's answer below is correct. This is arguably a defect in Grails itself, but certainly a documentation problem. I added a defect report to the Grails Jira https://jira.grails.org/browse/GRAILS-11595. I also added the test code (including fix) to GitHub: https://github.com/xnickmx/grails-2.4.2-plugin-integration-test-example


Original Question:

Note: this exact series of steps work if I create an app instead of a plugin.

Overview

My Grails plugin project has a service method that attempts to create a domain class. Calling the service's method from an integration test results in a

java.lang.IllegalStateException: Method on class [Domain class] was used outside of a Grails application.

Background

  1. Grails version 2.4.2 running on Windows 7
  2. Start from scratch with a Plugin project (example: GrailsTestPlugin)
  3. Create a simple domain class (example: Person)
  4. Create a simple service class (example: PersonService) with a method that creates domain class instances (example: createPerson)
  5. Create an integration test that calls the service class method (example: PersonServiceITSpec)
  6. Run the tests and get a NullPointerException. Symptoms very similar to GRAILS-10538 (but this bug mentions unit tests and is supposededly fixed). It fails this way in both Application and Plugin projects.
  7. Change the service's @Transactional annotation to static transactional = true and run the tests again. This time the tests fail with

    java.lang.IllegalStateException: Method on class [org.grails.testplugin.domain.Person] was used outside of a Grails application. If running in the context of a test using the mocking API or bootstrap Grails correctly. at org.grails.testplugin.service.PersonService.createPerson(PersonService.groovy:13)

If I change my service class to no longer attempt to create a domain class instance, it works.

What's happening?

This seems like basic Grails functionality. I'm surprised it doesn't seem to work in plugin projects. Am I doing something wrong? Is this not supposed to work? Is it a bug?

Details

org.grails.testplugin.domain.Person.groovy:

package org.grails.testplugin.domain

class Person {

    String name

    static constraints = {
    }
}

org.grails.testplugin.service.PersonService:

package org.grails.testplugin.service

import grails.transaction.Transactional
import org.grails.testplugin.domain.Person

//@Transactional have to comment out to avoid NPE
class PersonService {

    static transactional = true

    Person createPerson(String name) {

    new Person(name: name).save()
    }
}

Create integration test:

create-integration-test org.grails.testplugin.service.PersonServiceITSpec

contents:

package org.grails.testplugin.service

import spock.lang.*

import static org.junit.Assert.*

/**
 * Tests PersonService
 */
class PersonServiceITSpec extends Specification {

    private PersonService personService

    def setup() {
        personService = new PersonService()
    }

    def cleanup() {
    }

    void "test createPerson happy path"() {
        given:
        final name = "name"

        when:
        final result = personService.createPerson(name)

        then:
        assertNotNull(result)
        assertEquals(name, result.name)
        assertNotNull(result.getId())
    }
}

Run the tests

grails test-app integration:


Solution

  • Since you are running integration test, you do not need to do new for service. Your test should be something like this

    import spock.lang.*
    
    class PersonServiceITSpec extends Specification {
    
        PersonService personService
        def setup() {
        }
    
        def cleanup() {
        }
    
        void "test createPerson happy path"() {
            given:
            final String name = 'name'
    
            when:
            def result = personService.createPerson(name)
    
            then:
            result != null
            name == result.name
            result.id != null
        }
    }
    

    The framework will automatically inject personService in the field and it should not be private.

    Instead of extending Specification class, I would prefer extending IntegrationSpec.

    UPDATE:

    The reason for the error is the missing of hibernate plugin. When we create new plugin, hibernate plugin is not added in plugin dependency. So the test which contains domain will create problem. So add

    runtime ":hibernate4:4.3.5.4" // or ":hibernate:3.6.10.16"

    in BuildConfig.groovy in plugins dependency. It may look like this

    plugins { build(":release:3.0.1", ":rest-client-builder:1.0.3") { export = false } runtime ":hibernate4:4.3.5.4" // or ":hibernate:3.6.10.16" }

    It may be better to change the scope of hibernate to test.