15.1.3 Unit Testing Domains 3.1.4

See the light - agile, industrial strength, rapid web application development made easy

(Quick Reference) # 15.1.3 Unit Testing Domains - Reference Documentation Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari Version: 3.1.4 ## 15.1.3 Unit Testing Domains #### Overview Domain class interaction can be tested without involving a real database connection using `DomainClassUnitTestMixin` or by using the `HibernateTestMixin`. The GORM implementation in DomainClassUnitTestMixin is using a simple in-memory `ConcurrentHashMap` implementation. Note that this has limitations compared to a real GORM implementation. A large, commonly-used portion of the GORM API can be mocked using `DomainClassUnitTestMixin` including: Simple persistence methods like `save()`, `delete()` etc. Dynamic Finders Named Queries Query-by-example GORM Events `HibernateTestMixin` uses Hibernate 4 and a H2 in-memory database. This makes it possible to use all GORM features also in Grails unit tests. All features of GORM for Hibernate can be tested within a `HibernateTestMixin` unit test including: String-based HQL queries composite identifiers dirty checking methods any direct interaction with Hibernate The implementation behind `HibernateTestMixin` takes care of setting up the Hibernate with the in-memory H2 database. It only configures the given domain classes for use in a unit test. The @Domain annotation is used to tell which domain classes should be configured. #### DomainClassUnitTestMixin Basics `DomainClassUnitTestMixin` is typically used in combination with testing either a controller, service or tag library where the domain is a mock collaborator defined by the `Mock` annotation:

    import grails.test.mixin.TestFor
    import grails.test.mixin.Mock
    import spock.lang.Specification@TestFor(BookController)
    @Mock(Book)
    class BookControllerSpec extends Specification {
        // …
    }
The example above tests the `SimpleController` class and mocks the behavior of the `Simple` domain class as well. For example consider a typical scaffolded `save` controller action:
    class BookController {
        def save() {
            def book = new Book(params)
            if (book.save(flush: true)) {
                flash.message = message(
                        code: 'default.created.message',
                        args: [message(code: 'book.label', default: 'Book'), book.id])
                redirect(action: "show", id: book.id)
            }
            else {
                render(view: "create", model: [bookInstance: book])
            }
        }
    }
Tests for this action can be written as follows:

    import grails.test.mixin.TestFor
    import grails.test.mixin.Mock
    import spock.lang.Specification@TestFor(BookController)
    @Mock(Book)
    class BookControllerSpec extends Specification {
       void "test saving an invalid book"() {
            when:
            controller.save()        then:
            model.bookInstance != null
            view == '/book/create'
        }    void "test saving a valid book"() {
            when:
            params.title = "The Stand"
            params.pages = "500"        controller.save()        then:
            response.redirectedUrl == '/book/show/1'
            flash.message != null
            Book.count() == 1
        }
    }
`Mock` annotation also supports a list of mock collaborators if you have more than one domain to mock:

    import grails.test.mixin.TestFor
    import grails.test.mixin.Mock
    import spock.lang.Specification@TestFor(BookController)
    @Mock([Book, Author])
    class BookControllerSpec extends Specification {
        // …
    }
Alternatively you can also use the `DomainClassUnitTestMixin` directly with the `TestMixin` annotation and then call the `mockDomain` method to mock domains during your test:

    import grails.test.mixin.TestFor
    import grails.test.mixin.TestMixin
    import spock.lang.Specification
    import grails.test.mixin.domain.DomainClassUnitTestMixin@TestFor(BookController)
    @TestMixin(DomainClassUnitTestMixin)
    class BookControllerSpec extends Specification {    void setupSpec() {
             mockDomain(Book)
        }    void "test saving an invalid book"() {
            when:
            controller.save()        then:
            model.bookInstance != null
            view == '/book/create'
        }    void "test saving a valid book"() {
            when:
            params.title = "The Stand"
            params.pages = "500"        controller.save()        then:
            response.redirectedUrl == '/book/show/1'
            flash.message != null
            Book.count() == 1
        }
    }
The `mockDomain` method also includes an additional parameter that lets you pass a Map of Maps to configure a domain, which is useful for fixture-like data:
    mockDomain(Book, [
                [title: "The Stand", pages: 1000],
                [title: "The Shining", pages: 400],
                [title: "Along Came a Spider", pages: 300] ])
#### Testing Constraints There are 3 types of validateable classes: Domain classes Classes which implement the `Validateable` trait Command Objects which have been made validateable automatically These are all easily testable in a unit test with no special configuration necessary as long as the test method is marked with `TestFor` or explicitly applies the `GrailsUnitTestMixin` using `TestMixin`. See the examples below.

    // src/groovy/com/demo/MyValidateable.groovy
    package com.democlass MyValidateable implements grails.validation.Validateable {
        String name
        Integer age    static constraints = {
            name matches: /[A-Z].*/
            age range: 1..99
        }
    }

    // grails-app/domain/com/demo/Person.groovy
    package com.democlass Person {
        String name    static constraints = {
            name matches: /[A-Z].*/
        }
    }

    // grails-app/controllers/com/demo/DemoController.groovy
    package com.democlass DemoController {    def addItems(MyCommandObject co) {
            if(co.hasErrors()) {
                render 'something went wrong'
            } else {
                render 'items have been added'
            }
        }
    }class MyCommandObject {
        Integer numberOfItems    static constraints = {
            numberOfItems range: 1..10
        }
    }

    // test/unit/com/demo/PersonSpec.groovy
    package com.demoimport grails.test.mixin.TestFor
    import spock.lang.Specification@TestFor(Person)
    class PersonSpec extends Specification {    void "Test that name must begin with an upper case letter"() {
            when: 'the name begins with a lower letter'
            def p = new Person(name: 'jeff')        then: 'validation should fail'
            !p.validate()        when: 'the name begins with an upper case letter'
            p = new Person(name: 'Jeff')        then: 'validation should pass'
            p.validate()
        }
    }

    // test/unit/com/demo/DemoControllerSpec.groovy
    package com.demoimport grails.test.mixin.TestFor
    import spock.lang.Specification@TestFor(DemoController)
    class DemoControllerSpec extends Specification {    void 'Test an invalid number of items'() {
            when:
            params.numberOfItems = 42
            controller.addItems()        then:
            response.text == 'something went wrong'
        }    void 'Test a valid number of items'() {
            when:
            params.numberOfItems = 8
            controller.addItems()        then:
            response.text == 'items have been added'
        }
    }

    // test/unit/com/demo/MyValidateableSpec.groovy
    package com.demoimport grails.test.mixin.TestMixin
    import grails.test.mixin.support.GrailsUnitTestMixin
    import spock.lang.Specification
    @TestMixin(GrailsUnitTestMixin)
    class MyValidateableSpec extends Specification {    void 'Test validate can be invoked in a unit test with no special configuration'() {
            when: 'an object is valid'
            def validateable = new MyValidateable(name: 'Kirk', age: 47)        then: 'validate() returns true and there are no errors'
            validateable.validate()
            !validateable.hasErrors()
            validateable.errors.errorCount == 0        when: 'an object is invalid'
            validateable.name = 'kirk'        then: 'validate() returns false and the appropriate error is created'
            !validateable.validate()
            validateable.hasErrors()
            validateable.errors.errorCount == 1
            validateable.errors['name'].code == 'matches.invalid'        when: 'the clearErrors() is called'
            validateable.clearErrors()        then: 'the errors are gone'
            !validateable.hasErrors()
            validateable.errors.errorCount == 0        when: 'the object is put back in a valid state'
            validateable.name = 'Kirk'        then: 'validate() returns true and there are no errors'
            validateable.validate()
            !validateable.hasErrors()
            validateable.errors.errorCount == 0
        }
    }

    // test/unit/com/demo/MyCommandObjectSpec.groovy
    package com.demoimport grails.test.mixin.TestMixin
    import grails.test.mixin.support.GrailsUnitTestMixin
    import spock.lang.Specification@TestMixin(GrailsUnitTestMixin)
    class MyCommandObjectSpec extends Specification {    void 'Test that numberOfItems must be between 1 and 10'() {
            when: 'numberOfItems is less than 1'
            def co = new MyCommandObject()
            co.numberOfItems = 0        then: 'validation fails'
            !co.validate()
            co.hasErrors()
            co.errors['numberOfItems'].code == 'range.toosmall'        when: 'numberOfItems is greater than 10'
            co.numberOfItems = 11        then: 'validation fails'
            !co.validate()
            co.hasErrors()
            co.errors['numberOfItems'].code == 'range.toobig'        when: 'numberOfItems is greater than 1'
            co.numberOfItems = 1        then: 'validation succeeds'
            co.validate()
            !co.hasErrors()        when: 'numberOfItems is greater than 10'
            co.numberOfItems = 10        then: 'validation succeeds'
            co.validate()
            !co.hasErrors()
        }
    }
That's it for testing constraints. One final thing we would like to say is that testing the constraints in this way catches a common error: typos in the "constraints" property name which is a mistake that is easy to make and equally easy to overlook. A unit test for your constraints will highlight the problem straight away. #### HibernateTestMixin Basics `HibernateTestMixin` allows Hibernate 4 to be used in Grails unit tests. It uses a H2 in-memory database.

    import grails.test.mixin.TestMixin
    import grails.test.mixin.gorm.Domain
    import grails.test.mixin.hibernate.HibernateTestMixin
    import spock.lang.Specification
    @Domain(Person)
    @TestMixin(HibernateTestMixin)
    class PersonSpec extends Specification {    void "Test count people"() {
            expect: "Test execute Hibernate count query"
                Person.count() == 0
                sessionFactory != null
                transactionManager != null
                hibernateSession != null
        }
    }
This library dependency is required in build.gradle for adding support for `HibernateTestMixin`.
    dependencies {
            testCompile 'org.grails:grails-datastore-test-support:4.0.4.RELEASE'
        }
HibernateTestMixin is only supported with hibernate4 plugin versions >= 4.3.8.1 .
    dependencies {
            compile "org.grails.plugins:hibernate:4.3.8.1"
        }
#### Configuring domain classes for HibernateTestMixin tests The `grails.test.mixin.gorm.Domain` annotation is used to configure the list of domain classes to configure for Hibernate sessionFactory instance that gets configured when the unit test runtime is initialized. `Domain` annotations will be collected from several locations: the annotations on the test class the package annotations in the package-info.java/package-info.groovy file in the package of the test class each super class of the test class and their respective package annotations the possible `SharedRuntime` class `Domain` annotations can be shared by adding them as package annotations to package-info.java/package-info.groovy files or by adding them to a `SharedRuntime` class which has been added for the test. It's not possible to use DomainClassUnitTestMixin's `Mock` annotation in HibernateTestMixin tests. Use the `Domain` annotation in the place of `Mock` in HibernateTestMixin tests. | Quick Reference (hide)