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

(Quick Reference) # 12.1.3 Unit Testing Domains - Reference Documentation Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari Version: 2.3.8 ## 12.1.3 Unit Testing Domains #### Overview The mocking support described here is best used when testing non-domain artifacts that use domain classes, to let you focus on testing the artifact without needing a database. But when testing persistence it's best to use integration tests which configure Hibernate and use a database. Domain class interaction can be tested without involving a database connection using `DomainClassUnitTestMixin`. This implementation mimics the behavior of GORM against an in-memory `ConcurrentHashMap` implementation. Note that this has limitations compared to a real GORM implementation. The following features of GORM for Hibernate can only be tested within an integration test: String-based HQL queries composite identifiers dirty checking methods any direct interaction with Hibernate However 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 If something isn't supported then `GrailsUnitTestMixin`'s `mockFor` method can come in handy to mock the missing pieces. Alternatively you can write an integration test which bootstraps the complete Grails environment at a cost of test execution time. #### The 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 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 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 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 4 types of validateable classes: Domain classes Classes marked with the `Validateable` annotation Command Objects which have been made validateable automatically Classes configured to be validateable via the `grails.validateable.classes` property in `Config.groovy` The first 3 are 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.demo@grails.validation.Validateable
    class MyValidateable {
        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()
        }
    }
For validateable classes which are not one of the first 3 types listed above but are configured with the `grails.validateable.classes` property in `Config.groovy`, one additional step is required to test validation. `GrailsUnitTestMixin` provides a method named `mockForConstraintsTests` that will mock validation support for these classes. See the example below.

    // src/groovy/com/demo/Book.groovy
    package com.democlass Book {
        String title
        String author    static constraints = {
            author minSize: 5
        }
    }

    // grails-app/conf/Config.groovy
    grails.validateable.classes = [com.demo.Book]// ...

    // test/unit/com/demo/BookSpec.groovy
    package com.demoimport grails.test.mixin.TestMixin
    import grails.test.mixin.support.GrailsUnitTestMixin
    import spock.lang.Specification@TestMixin(GrailsUnitTestMixin)
    class BookSpec extends Specification {    void 'Test validation'() {
            given:
            mockForConstraintsTests Book        when: 'the author name has only 4 characters'
            def book = new Book()
            book.author = 'Jeff'        then: 'validation should fail'
            !book.validate()
            book.hasErrors()
            book.errors['author'] == 'minSize'        when: 'the author name has 5 characters'
            book.author = 'Jacob'        then: 'validation should pass'
            book.validate()
            !book.hasErrors()
        }
    }
Note that the `mockForConstraintsTests` method changes the behavior of the errors object such that something like `book.errors'author'` will evaluate to the name of the failed constraint, not a `org.springframework.validation.FieldError` object. This is convenient for unit tests. If your unit test really does want a reference to the `org.springframework.validation.FieldError` object use something like `book.errors.getFieldError('author')`. 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. | Quick Reference (hide)