15.1 Unit Testing - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.1.7
Table of Contents
15.1 Unit Testing
Unit testing are tests at the "unit" level. In other words you are testing individual methods or blocks of code without consideration for surrounding infrastructure. Unit tests are typically run without the presence of physical resources that involve I/O such databases, socket connections or files. This is to ensure they run as quick as possible since quick feedback is important.The Test Mixins
Since Grails 2.0, a collection of unit testing mixins is provided by Grails that lets you enhance the behavior of a typical JUnit 3, JUnit 4 or Spock test. The following sections cover the usage of these mixins.
The previous JUnit 3-style GrailsUnitTestCase
class hierarchy is still present in Grails for backwards compatibility, but is now deprecated. The previous documentation on the subject can be found in the Grails 1.3.x documentation
You won't normally have to import any of the testing classes because Grails does that for you. But if you find that your IDE for example can't find the classes, here they all are:
grails.test.mixin.TestFor
grails.test.mixin.Mock
grails.test.mixin.TestMixin
grails.test.mixin.support.GrailsUnitTestMixin
grails.test.mixin.domain.DomainClassUnitTestMixin
grails.test.mixin.services.ServiceUnitTestMixin
grails.test.mixin.web.ControllerUnitTestMixin
grails.test.mixin.web.FiltersUnitTestMixin
grails.test.mixin.web.GroovyPageUnitTestMixin
grails.test.mixin.web.UrlMappingsUnitTestMixin
grails.test.mixin.hibernate.HibernateTestMixin
Test Mixin Basics
Most testing can be achieved via theTestFor
annotation in combination with the Mock
annotation for mocking collaborators. For example, to test a controller and associated domains you would define the following:@TestFor(BookController) @Mock([Book, Author, BookService])
TestFor
annotation defines the class under test and will automatically create a field for the type of class under test. For example in the above case a "controller" field will be present, however if TestFor
was defined for a service a "service" field would be created and so on.The Mock
annotation creates mock version of any collaborators. There is an in-memory implementation of GORM that will simulate most interactions with the GORM API.doWithSpring and doWithConfig callback methods, FreshRuntime annotation
ThedoWithSpring
callback method can be used to add beans with the BeanBuilder DSL. There is the doWithConfig
callback method for changing the grailsApplication.config values before the grailsApplication instance of the test runtime gets initialized.import grails.test.mixin.support.GrailsUnitTestMixinimport org.junit.ClassRule import org.junit.rules.TestRuleimport spock.lang.Ignore; import spock.lang.IgnoreRest import spock.lang.Shared; import spock.lang.Specification@TestMixin(GrailsUnitTestMixin) class StaticCallbacksSpec extends Specification { static doWithSpring = { myService(MyService) } static doWithConfig(c) { c.myConfigValue = 'Hello' } def "grailsApplication is not null"() { expect: grailsApplication != null } def "doWithSpring callback is executed"() { expect: grailsApplication.mainContext.getBean('myService') != null } def "doWithConfig callback is executed"(){ expect: config.myConfigValue == 'Hello' } }
grails.test.runtime.FreshRuntime
annotation.
In this case, a clean application context and grails application instance is initialized for each test method call.import grails.test.mixin.support.GrailsUnitTestMixin import grails.test.runtime.FreshRuntime;import org.junit.ClassRule import org.junit.rules.TestRuleimport spock.lang.Ignore; import spock.lang.IgnoreRest import spock.lang.Shared; import spock.lang.Specification@FreshRuntime @TestMixin(GrailsUnitTestMixin) class TestInstanceCallbacksSpec extends Specification { def doWithSpring = { myService(MyService) } def doWithConfig(c) { c.myConfigValue = 'Hello' } def "grailsApplication is not null"() { expect: grailsApplication != null } def "doWithSpring callback is executed"() { expect: grailsApplication.mainContext.getBean('myService') != null } def "doWithConfig callback is executed"(){ expect: config.myConfigValue == 'Hello' } }
org.grails.spring.beans.factory.InstanceFactoryBean
together with doWithSpring and the FreshRuntime annotation to mock beans in tests.import grails.test.mixin.support.GrailsUnitTestMixin import grails.test.runtime.FreshRuntimeimport org.grails.spring.beans.factory.InstanceFactoryBean import org.junit.ClassRuleimport spock.lang.Shared import spock.lang.Specification@FreshRuntime @TestMixin(GrailsUnitTestMixin) class MockedBeanSpec extends Specification { def myService=Mock(MyService) def doWithSpring = { myService(InstanceFactoryBean, myService, MyService) } def "doWithSpring callback is executed"() { when: def myServiceBean=grailsApplication.mainContext.getBean('myService') myServiceBean.prova() then: 1 * myService.prova() >> { true } } }
The DirtiesRuntime annotation
Test methods may be marked with thegrails.test.runtime.DirtiesRuntime
annotation to indicate that the test modifies the runtime in ways which might be problematic for other tests and as such the runtime should be refreshed after this test method runs.import grails.test.mixin.TestFor import spock.lang.Specification import grails.test.runtime.DirtiesRuntime@TestFor(PersonController) class PersonControllerSpec extends Specification { @DirtiesRuntime void "a test method which modifies the runtime"() { when: Person.metaClass.someMethod = { … } // ... then: // … } void "a test method which should not be affected by the previous test method"() { // … } }
Sharing test runtime grailsApplication instance and beans for several test classes
It's possible to share a single grailsApplication instance and beans for several test classes. This feature is activated by theSharedRuntime
annotation. This annotation takes an optional class parameter
implements SharedRuntimeConfigurer
interface. All test classes referencing the same SharedRuntimeConfigurer implementation
class will share the same runtime during a single test run.
The value class for SharedRuntimeConfigurer annotation can also implement TestEventInterceptor
. In this case the instance of the class
will be registered as a test event interceptor for the test runtime.
Loading application beans in unit tests
Addingstatic loadExternalBeans = true
field definition to a unit test class makes the Grails unit test runtime load all bean definitions from grails-app/conf/spring/resources.groovy
and grails-app/conf/spring/resources.xml
files.import spock.lang.Issue import spock.lang.Specification import grails.test.mixin.support.GrailsUnitTestMixin@TestMixin(GrailsUnitTestMixin) class LoadExternalBeansSpec extends Specification { static loadExternalBeans = true void "should load external beans"(){ expect: applicationContext.getBean('simpleBean') == 'Hello world!' } }
15.1.1 Unit Testing Controllers
The Basics
You use thegrails.test.mixin.TestFor
annotation to unit test controllers. Using TestFor
in this manner activates the grails.test.mixin.web.ControllerUnitTestMixin
and its associated API. For example:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void "test something"() { } }
TestFor
annotation to a controller causes a new controller
field to be automatically created for the controller under test.
The TestFor
annotation will also automatically annotate any public methods starting with "test" with JUnit 4's @Test annotation. If any of your test method don't start with "test" just add this manually
To test the simplest "Hello World"-style example you can do the following:// Test class
class SimpleController {
def hello() {
render "hello"
}
}
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void "test hello"() { when: controller.hello() then: response.text == 'hello' } }
response
object is an instance of GrailsMockHttpServletResponse
(from the package org.codehaus.groovy.grails.plugins.testing
) which extends Spring's MockHttpServletResponse
class and has a number of useful methods for inspecting the state of the response.For example to test a redirect you can use the redirectedUrl
property:class SimpleController { def index() { redirect action: 'hello' } … }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test index'() { when: controller.index() then: response.redirectedUrl == '/simple/hello' } }
params
variable:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(PersonController) class PersonControllerSpec extends Specification { void 'test list'() { when: params.sort = 'name' params.max = 20 params.offset = 0 controller.list() then: // … } }
method
property of the mock request:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(PersonController) class PersonControllerSpec extends Specification { void 'test save'() { when: request.method = 'POST' controller.save() then: // … } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(PersonController) class PersonControllerSpec extends Specification { void 'test list'() { when: request.method = 'POST' request.makeAjaxRequest() controller.getPage() then: // … } }
xhr
property on the request.Testing View Rendering
To test view rendering you can inspect the state of the controller'smodelAndView
property (an instance of org.springframework.web.servlet.ModelAndView
) or you can use the view
and model
properties provided by the mixin:class SimpleController { def home() { render view: "homePage", model: [title: "Hello World"] } … }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test home'() { when: controller.home() then: view == '/simple/homePage' model.title == 'Hello World' } }
Testing Template Rendering
Unlike view rendering, template rendering will actually attempt to write the template directly to the response rather than returning aModelAndView
hence it requires a different approach to testing.Consider the following controller action:class SimpleController {
def display() {
render template:"snippet"
}
}
grails-app/views/simple/_snippet.gsp
. You can test this as follows:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test display'() { when: controller.display() then: response.text == 'contents of the template' } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test display with mock template'() { when: views['/simple/_snippet.gsp'] = 'mock template contents' controller.display() then: response.text == 'mock template contents' } }
Testing Actions Which Return A Map
When a controller action returns ajava.util.Map
that Map
may be inspected directly to assert that it contains the expected data:class SimpleController { def showBookDetails() { [title: 'The Nature Of Necessity', author: 'Alvin Plantinga'] } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test show book details'() { when: def model = controller.showBookDetails() then: model.author == 'Alvin Plantinga' } }
Testing XML and JSON Responses
XML and JSON response are also written directly to the response. Grails' mocking capabilities provide some conveniences for testing XML and JSON response. For example consider the following action:def renderXml() { render(contentType:"text/xml") { book(title:"Great") } }
xml
property of the response:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test render xml'() { when: controller.renderXml() then: response.text == "<book title='Great'/>" response.xml.@title.text() == 'Great' } }
xml
property is a parsed result from Groovy's XmlSlurper class which is very convenient for parsing XML.Testing JSON responses is pretty similar, instead you use the json
property:// controller action def renderJson() { render(contentType:"application/json") { book = "Great" } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test render json'() { when: controller.renderJson() then: response.text == '{"book":"Great"}' response.json.book == 'Great' } }
json
property is an instance of org.codehaus.groovy.grails.web.json.JSONElement
which is a map-like structure that is useful for parsing JSON responses.Testing XML and JSON Requests
Grails provides various convenient ways to automatically parse incoming XML and JSON packets. For example you can bind incoming JSON or XML requests using Grails' data binding:def consumeBook(Book b) {
render "The title is ${b.title}."
}
xml
or json
properties. For example the above action can be tested by specifying a String containing the XML:import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock([Book]) class SimpleControllerSpec extends Specification { void 'test consume book xml'() { when: request.xml = '<book><title>Wool</title></book>' controller.consumeBook() then: response.text == 'The title is Wool.' } }
import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock([Book]) class SimpleControllerSpec extends Specification { void 'test consume book xml'() { when: request.xml = new Book(title: 'Shift') controller.consumeBook() then: response.text == 'The title is Shift.' } }
import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock([Book]) class SimpleControllerSpec extends Specification { void 'test consume book json'() { when: request.json = new Book(title: 'Shift') controller.consumeBook() then: response.text == 'The title is Shift.' } }
def consume() { request.withFormat { xml { render "The XML Title Is ${request.XML.@title}." } json { render "The JSON Title Is ${request.JSON.title}." } } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test consume xml'() { when: request.xml = '<book title="The Stand"/>' controller.consume() then: response.text == 'The XML Title Is The Stand.' } void 'test consume json'() { when: request.json = '{title:"The Stand"}' controller.consume() then: response.text == 'The JSON Title Is The Stand.' } }
Testing Mime Type Handling
You can test mime type handling and thewithFormat
method quite simply by setting the request's contentType
attribute:// controller action
def sayHello() {
def data = [Hello:"World"]
request.withFormat {
xml { render data as grails.converters.XML }
json { render data as grails.converters.JSON }
html data
}
}
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test say hello xml'() { when: request.contentType = 'application/xml' controller.sayHello() then: response.text == '<?xml version="1.0" encoding="UTF-8"?><map><entry key="Hello">World</entry></map>' } void 'test say hello json'() { when: request.contentType = 'application/json' controller.sayHello() then: response.text == '{"Hello":"World"}' } }
ControllerUnitTestMixin
for all of the common common content types as shown below:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test say hello xml'() { when: request.contentType = XML_CONTENT_TYPE controller.sayHello() then: response.text == '<?xml version="1.0" encoding="UTF-8"?><map><entry key="Hello">World</entry></map>' } void 'test say hello json'() { when: request.contentType = JSON_CONTENT_TYPE controller.sayHello() then: response.text == '{"Hello":"World"}' } }
Constant | Value |
---|---|
ALL_CONTENT_TYPE | */* |
FORM_CONTENT_TYPE | application/x-www-form-urlencoded |
MULTIPART_FORM_CONTENT_TYPE | multipart/form-data |
HTML_CONTENT_TYPE | text/html |
XHTML_CONTENT_TYPE | application/xhtml+xml |
XML_CONTENT_TYPE | application/xml |
JSON_CONTENT_TYPE | application/json |
TEXT_XML_CONTENT_TYPE | text/xml |
TEXT_JSON_CONTENT_TYPE | text/json |
HAL_JSON_CONTENT_TYPE | application/hal+json |
HAL_XML_CONTENT_TYPE | application/hal+xml |
ATOM_XML_CONTENT_TYPE | application/atom+xml |
Testing Duplicate Form Submissions
Testing duplicate form submissions is a little bit more involved. For example if you have an action that handles a form such as:def handleForm() { withForm { render "Good" }.invalidToken { render "Bad" } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test duplicate form submission'() { when: controller.handleForm() then: response.text == 'Bad' } }
SynchronizerToken
:import grails.test.mixin.TestFor import spock.lang.Specificationimport org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerTokensHolder@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test valid form submission'() { when: def tokenHolder = SynchronizerTokensHolder.store(session) params[SynchronizerTokensHolder.TOKEN_URI] = '/controller/handleForm' params[SynchronizerTokensHolder.TOKEN_KEY] = tokenHolder.generateToken(params[SynchronizerTokensHolder.TOKEN_URI]) controller.handleForm() then: response.text == 'Good' } }
import grails.test.mixin.TestFor import spock.lang.Specificationimport org.codehaus.groovy.grails.web.servlet.mvc.SynchronizerTokensHolder@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test form submission'() { when: controller.handleForm() then: response.text == 'Bad' when: response.reset() def tokenHolder = SynchronizerTokensHolder.store(session) params[SynchronizerTokensHolder.TOKEN_URI] = '/controller/handleForm' params[SynchronizerTokensHolder.TOKEN_KEY] = tokenHolder.generateToken(params[SynchronizerTokensHolder.TOKEN_URI]) controller.handleForm() then: response.text == 'Good' } }
Testing File Upload
You use theGrailsMockMultipartFile
class to test file uploads. For example consider the following controller action:def uploadFile() { MultipartFile file = request.getFile("myFile") file.transferTo(new File("/local/disk/myFile")) }
GrailsMockMultipartFile
with the request:import grails.test.mixin.TestFor import spock.lang.Specificationimport org.codehaus.groovy.grails.plugins.testing.GrailsMockMultipartFile@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test file upload'() { when: def file = new GrailsMockMultipartFile('myFile', 'some file contents'.bytes) request.addFile file controller.uploadFile() then: file.targetFileLocation.path == '/local/disk/myFile' } }
GrailsMockMultipartFile
constructor arguments are the name and contents of the file. It has a mock implementation of the transferTo
method that simply records the targetFileLocation
and doesn't write to disk.Testing Command Objects
Special support exists for testing command object handling with themockCommandObject
method. For example consider the following action:class SimpleController { def handleCommand(SimpleCommand simple) { if(simple.hasErrors()) { render 'Bad' } else { render 'Good' } } }class SimpleCommand { String name static constraints = { name blank: false } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test valid command object'() { given: def simpleCommand = new SimpleCommand(name: 'Hugh') simpleCommand.validate() when: controller.handleCommand(simpleCommand) then: response.text == 'Good' } void 'test invalid command object'() { given: def simpleCommand = new SimpleCommand(name: '') simpleCommand.validate() when: controller.handleCommand(simpleCommand) then: response.text == 'Bad' } }
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test valid command object'() { when: params.name = 'Hugh' controller.handleCommand() then: response.text == 'Good' } void 'test invalid command object'() { when: params.name = '' controller.handleCommand() then: response.text == 'Bad' } }
Testing allowedMethods
The unit testing environment respects the allowedMethods property in controllers. If a controller action is limited to be accessed with certain request methods, the unit test must be constructed to deal with that.// grails-app/controllers/com/demo/DemoController.groovypackage com.democlass DemoController { static allowedMethods = [save: 'POST', update: 'PUT', delete: 'DELETE'] def save() {
render 'Save was successful!'
} // …
}
// test/unit/com/demo/DemoControllerSpec.groovy package com.demoimport grails.test.mixin.TestFor import spock.lang.Specification import static javax.servlet.http.HttpServletResponse.*@TestFor(DemoController) class DemoControllerSpec extends Specification { void "test a valid request method"() { when: request.method = 'POST' controller.save() then: response.status == SC_OK response.text == 'Save was successful!' } void "test an invalid request method"() { when: request.method = 'DELETE' controller.save() then: response.status == SC_METHOD_NOT_ALLOWED } }
Testing Calling Tag Libraries
You can test calling tag libraries usingControllerUnitTestMixin
, although the mechanism for testing the tag called varies from tag to tag. For example to test a call to the message
tag, add a message to the messageSource
. Consider the following action:def showMessage() {
render g.message(code: "foo.bar")
}
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleController) class SimpleControllerSpec extends Specification { void 'test render message tag'() { given: messageSource.addMessage 'foo.bar', request.locale, 'Hello World' when: controller.showMessage() then: response.text == 'Hello World' } }
15.1.2 Unit Testing Tag Libraries
The Basics
Tag libraries and GSP pages can be tested with thegrails.test.mixin.web.GroovyPageUnitTestMixin
mixin. To use the mixin declare which tag library is under test with the TestFor
annotation:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleTagLib) class SimpleTagLibSpec extends Specification { void "test something"() { } }
TestFor
annotation to a TagLib class causes a new tagLib
field to be automatically created for the TagLib class under test.
The tagLib field can be used to test calling tags as function calls. The return value of a function call is either a StreamCharBuffer instance or
the object returned from the tag closure when returnObjectForTags feature is used.Note that if you are testing invocation of a custom tag from a controller you can combine the ControllerUnitTestMixin
and the GroovyPageUnitTestMixin
using the Mock
annotation:import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock(SimpleTagLib) class SimpleControllerSpec extends Specification {}
Testing Custom Tags
The core Grails tags don't need to be enabled during testing, however custom tag libraries do. TheGroovyPageUnitTestMixin
class provides a mockTagLib()
method that you can use to mock a custom tag library. For example consider the following tag library:class SimpleTagLib { static namespace = 's' def hello = { attrs, body -> out << "Hello ${attrs.name ?: 'World'}" } def bye = { attrs, body -> out << "Bye ${attrs.author.name ?: 'World'}" } }
TestFor
and supplying the name of the tag library:import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleTagLib) class SimpleTagLibSpec extends Specification { void "test hello tag"() { expect: applyTemplate('<s:hello />') == 'Hello World' applyTemplate('<s:hello name="Fred" />') == 'Hello Fred' applyTemplate('<s:bye author="${author}" />', [author: new Author(name: 'Fred')]) == 'Bye Fred' } void "test tag calls"() { expect: tagLib.hello().toString() == 'Hello World' tagLib.hello(name: 'Fred').toString() == 'Hello Fred' tagLib.bye(author: new Author(name: 'Fred')).toString == 'Bye Fred' } }
TestMixin
annotation and mock multiple tag libraries using the mockTagLib()
method:import spock.lang.Specification import grails.test.mixin.TestMixin import grails.test.mixin.web.GroovyPageUnitTestMixin@TestMixin(GroovyPageUnitTestMixin) class MultipleTagLibSpec extends Specification { void "test multiple tags"() { given: mockTagLib(SomeTagLib) mockTagLib(SomeOtherTagLib) expect: // … } }
GroovyPageUnitTestMixin
provides convenience methods for asserting that the template output equals or matches an expected value.import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(SimpleTagLib) class SimpleTagLibSpec extends Specification { void "test hello tag"() { expect: assertOutputEquals ('Hello World', '<s:hello />') assertOutputMatches (/.*Fred.*/, '<s:hello name="Fred" />') } }
Testing View and Template Rendering
You can test rendering of views and templates ingrails-app/views
via the render(Map)
method provided by GroovyPageUnitTestMixin
:import spock.lang.Specification import grails.test.mixin.TestMixin import grails.test.mixin.web.GroovyPageUnitTestMixin@TestMixin(GroovyPageUnitTestMixin) class RenderingSpec extends Specification { void "test rendering template"() { when: def result = render(template: '/simple/hello') then: result == 'Hello World!' } }
grails-app/views/simple/_hello.gsp
. Note that if the template depends on any custom tag libraries you need to call mockTagLib
as described in the previous section.Some core tags use the active controller and action as input. In GroovyPageUnitTestMixin tests, you can manually set the active controller and action name by setting controllerName and actionName properties on the webRequest object:webRequest.controllerName = 'simple' webRequest.actionName = 'hello'
15.1.3 Unit Testing Domains
Overview
Domain class interaction can be tested without involving a real database connection usingDomainClassUnitTestMixin
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
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 { // … }
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]) } } }
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 { // … }
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 } }
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
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() } }
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 } }
HibernateTestMixin
.dependencies { testCompile 'org.grails:grails-datastore-test-support:4.0.4.RELEASE' }
dependencies {
compile "org.grails.plugins:hibernate:4.3.8.1"
}
Configuring domain classes for HibernateTestMixin tests
Thegrails.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.
15.1.4 Unit Testing Filters
Unit testing filters is typically a matter of testing a controller where a filter is a mock collaborator. For example consider the following filters class:class CancellingFilters { def filters = { all(controller:"simple", action:"list") { before = { redirect(controller:"book") return false } } } }
list
action of the simple
controller and redirects to the book
controller. To test this filter you start off with a test that targets the SimpleController
class and add the CancellingFilters
as a mock collaborator:import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock(CancellingFilters) class SimpleControllerSpec extends Specification { // ...}
withFilters
method to wrap the call to an action in filter execution:import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(SimpleController) @Mock(CancellingFilters) class SimpleControllerSpec extends Specification { void "test list action is filtered"() { when: withFilters(action:"list") { controller.list() } then: response.redirectedUrl == '/book' } }
action
parameter is required because it is unknown what the action to invoke is until the action is actually called. The controller
parameter is optional and taken from the controller under test. If it is another controller you are testing then you can specify it:withFilters(controller:"book",action:"list") { controller.list() }
15.1.5 Unit Testing URL Mappings
The Basics
Testing URL mappings can be done with theTestFor
annotation testing a particular URL mappings class. For example to test the default URL mappings you can do the following:import com.demo.SimpleController import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(UrlMappings) @Mock(SimpleController) class UrlMappingsSpec extends Specification { // … }
@Mock
annotation.
Note that since the default UrlMappings
class is in the default package your test must also be in the default package
With that done there are a number of useful methods that are defined by the grails.test.mixin.web.UrlMappingsUnitTestMixin
for testing URL mappings. These include:
assertForwardUrlMapping
- Asserts a URL mapping is forwarded for the given controller class (note that controller will need to be defined as a mock collaborate for this to work)assertReverseUrlMapping
- Asserts that the given URL is produced when reverse mapping a link to a given controller and actionassertUrlMapping
- Asserts a URL mapping is valid for the given URL. This combines theassertForwardUrlMapping
andassertReverseUrlMapping
assertions
Asserting Forward URL Mappings
You useassertForwardUrlMapping
to assert that a given URL maps to a given controller. For example, consider the following URL mappings:static mappings = { "/actionOne"(controller: "simple", action: "action1") "/actionTwo"(controller: "simple", action: "action2") }
import com.demo.SimpleController import grails.test.mixin.TestFor import grails.test.mixin.Mock import spock.lang.Specification@TestFor(UrlMappings) @Mock(SimpleController) class UrlMappingsSpec extends Specification { void "test forward mappings"() { expect: assertForwardUrlMapping("/actionOne", controller: 'simple', action: "action1") assertForwardUrlMapping("/actionTwo", controller: 'simple', action: "action2") } }
Assert Reverse URL Mappings
You useassertReverseUrlMapping
to check that correct links are produced for your URL mapping when using the link
tag in GSP views. An example test is largely identical to the previous listing except you use assertReverseUrlMapping
instead of assertForwardUrlMapping
. Note that you can combine these 2 assertions with assertUrlMapping
.
15.1.6 Mocking Collaborators
The Spock Framework manual has a chapter on Interaction Based Testing which also explains mocking collaborators.15.1.7 Mocking Codecs
TheGrailsUnitTestMixin
provides a mockCodec
method for mocking custom codecs which may be invoked while a unit test is running.mockCodec(MyCustomCodec)
15.1.8 Unit Test Metaprogramming
If runtime metaprogramming needs to be done in a unit test it needs to be done early in the process before the unit testing environment is fully initialized. This should be done when the unit test class is being initialized. For a Spock based test this should be done in thesetupSpec()
method. For a JUnit test this should be done in a method marked with @BeforeClass
.
package myappimport grails.test.mixin.* import spock.lang.Specification@TestFor(SomeController) class SomeControllerSpec extends Specification { def setupSpec() { SomeClass.metaClass.someMethod = { -> // do something here } } // … }
package myappimport grails.test.mixin.* import org.junit.*@TestFor(SomeController) class SomeControllerTests { @BeforeClass static void metaProgramController() { SomeClass.metaClass.someMethod = { -> // do something here } } // ...}