12 Testing - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 2.3.9
Table of Contents
12 Testing
Automated testing is a key part of Grails. Hence, Grails provides many ways to making testing easier from low level unit testing to high level functional tests. This section details the different capabilities that Grails offers for testing.
Grails 1.3.x and below used the grails.test.GrailsUnitTestCase class hierarchy for testing in a JUnit 3 style. Grails 2.0.x and above deprecates these test harnesses in favour of mixins that can be applied to a range of different kinds of tests (JUnit 3, Junit 4, Spock etc.) without subclassing
The first thing to be aware of is that all of the create-* and generate-* commands create unit or integration tests automatically. For example if you run the create-controller command as follows:grails create-controller com.acme.app.simple
grails-app/controllers/com/acme/app/SimpleController.groovy, and also a unit test at test/unit/com/acme/app/SimpleControllerTests.groovy. What Grails won't do however is populate the logic inside the test! That is left up to you.The default class name suffix isTestsbut as of Grails 1.2.2, the suffix ofTestis also supported.
Running Tests
Tests are run with the test-app command:grails test-app
------------------------------------------------------- Running Unit Tests… Running test FooTests...FAILURE Unit Tests Completed in 464ms … -------------------------------------------------------Tests failed: 0 errors, 1 failures
You can force a clean before running tests by passingGrails writes both plain text and HTML test reports to the-cleanto thetest-appcommand.
target/test-reports directory, along with the original XML files. The HTML reports are generally the best ones to look at.Using Grails' interactive mode confers some distinct advantages when executing tests. First, the tests will execute significantly faster on the second and subsequent runs. Second, a shortcut is available to open the HTML reports in your browser:open test-report
Targeting Tests
You can selectively target the test(s) to be run in different ways. To run all tests for a controller namedSimpleController you would run:grails test-app SimpleController
SimpleController. Wildcards can be used...grails test-app *Controller
Controller. Package names can optionally be specified...grails test-app some.org.*Controller
grails test-app some.org.*
grails test-app some.org.**.*
grails test-app SimpleController.testLogin
testLogin test in the SimpleController tests. You can specify as many patterns in combination as you like...grails test-app some.org.* SimpleController.testLogin BookController
Targeting Test Types and/or Phases
In addition to targeting certain tests, you can also target test types and/or phases by using thephase:type syntax.Grails organises tests by phase and by type. A test phase relates to the state of the Grails application during the tests, and the type relates to the testing mechanism.Grails comes with support for 4 test phases (To execute the JUnitunit,integration,functionalandother) and JUnit test types for theunitandintegrationphases. These test types have the same name as the phase.Testing plugins may provide new test phases or new test types for existing phases. Refer to the plugin documentation.
integration tests you can run:grails test-app integration:integration
phase and type are optional. Their absence acts as a wildcard. The following command will run all test types in the unit phase:grails test-app unit:
spock test type to the unit, integration and functional phases. To run all spock tests in all phases you would run the following:grails test-app :spock
functional phase you would run...grails test-app functional:spock
grails test-app unit:spock integration:spock
Targeting Tests in Types and/or Phases
Test and type/phase targetting can be applied at the same time:grails test-app integration: unit: some.org.**.*
integration and unit phases that are in the package some.org or a subpackage.
12.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.TestForgrails.test.mixin.Mockgrails.test.mixin.TestMixingrails.test.mixin.support.GrailsUnitTestMixingrails.test.mixin.domain.DomainClassUnitTestMixingrails.test.mixin.services.ServiceUnitTestMixingrails.test.mixin.web.ControllerUnitTestMixingrails.test.mixin.web.FiltersUnitTestMixingrails.test.mixin.web.GroovyPageUnitTestMixingrails.test.mixin.web.UrlMappingsUnitTestMixingrails.test.mixin.webflow/WebFlowUnitTestMixin
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. For those interactions that are not automatically mocked you can use the built in support for defining mocks and stubs programmatically. For example:
import grails.test.mixin.TestFor import spock.lang.Specification@TestFor(BookController) @Mock(Book) class BookControllerSpec extends Specification { void "test search"() { given: def searchMock = mockFor(SearchService) searchMock.demand.searchWeb { String q -> ['first result', 'second result'] } searchMock.demand.static.logResults { List results -> } controller.searchService = searchMock.createMock() when: controller.search() then: controller.response.text.contains "Found 2 results" } }
12.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 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 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 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 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' } }
12.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"() { } }
ControllerUnitTestMixin and the GroovyPageUnitTestMixin using the Mock annotation: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' } }
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'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
DomainClassUnitTestMixin including:
- Simple persistence methods like
save(),delete()etc. - Dynamic Finders
- Named Queries
- Query-by-example
- GORM Events
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 { // … }
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 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 { // … }
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 4 types of validateable classes:- Domain classes
- Classes marked with the
Validateableannotation - Command Objects which have been made validateable automatically
- Classes configured to be validateable via the
grails.validateable.classesproperty inConfig.groovy
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() } }
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() } }
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.
12.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 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 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() }
12.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 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 theassertForwardUrlMappingandassertReverseUrlMappingassertions
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 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.
12.1.6 Mocking Collaborators
Beyond the specific targeted mocking APIs there is also an all-purposemockFor() method that is available when using the TestFor annotation. The signature of mockFor is:mockFor(class, loose = false)def strictControl = mockFor(MyService)
strictControl.demand.someMethod(0..2) { String arg1, int arg2 -> … }
strictControl.demand.static.aStaticMethod {-> … }mockControl.createMock() to get an actual mock instance of the class that you are mocking. You can call this multiple times to create as many mock instances as you need. And once you have executed the test method, call mockControl.verify() to check that the expected methods were called.Grails mocks also provide a demandExplicit method that can be used in place of demand. This will check the mocked class's metaClass and throw an ExplicitDemandException if a method with that name and signature doesn't exist. For example, given the service:class MyService {
def someMethod(String s) { … }
}def strictControl = mockFor(MyService)
//Works just like the demand method since method signature exists on the class
strictControl.demandExplicit.someMethod(1) { String arg1 }def strictControl = mockFor(MyService)
//Throws ExplicitDemandException because method signature doesn't exist
strictControl.demandExplicit.someMethod(1) { String arg1, String arg2 }def looseControl = mockFor(MyService, true)12.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)
12.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 } } // ...}
12.2 Integration Testing
Integration tests differ from unit tests in that you have full access to the Grails environment within the test. Grails uses an in-memory H2 database for integration tests and clears out all the data from the database between tests.One thing to bear in mind is that logging is enabled for your application classes, but it is different from logging in tests. So if you have something like this:class MyServiceTests extends GroovyTestCase { void testSomething() { log.info "Starting tests" … } }
log property in the example above is an instance of java.util.logging.Logger (inherited from the base class, not injected by Grails), which doesn't have the same methods as the log property injected into your application artifacts. For example, it doesn't have debug() or trace() methods, and the equivalent of warn() is in fact warning().Transactions
Integration tests run inside a database transaction by default, which is rolled back at the end of the each test. This means that data saved during a test is not persisted to the database. Add atransactional property to your test class to check transactional behaviour:class MyServiceTests extends GroovyTestCase { static transactional = false void testMyTransactionalServiceMethod() { … } }
tearDown method, so these tests don't interfere with standard transactional tests that expect a clean database.Testing Controllers
To test controllers you first have to understand the Spring Mock Library.Grails automatically configures each test with a MockHttpServletRequest, MockHttpServletResponse, and MockHttpSession that you can use in your tests. For example consider the following controller:class FooController { def text() {
render "bar"
} def someRedirect() {
redirect(action:"bar")
}
}class FooControllerTests extends GroovyTestCase { void testText() { def fc = new FooController() fc.text() assertEquals "bar", fc.response.contentAsString } void testSomeRedirect() { def fc = new FooController() fc.someRedirect() assertEquals "/foo/bar", fc.response.redirectedUrl } }
response is an instance of MockHttpServletResponse which we can use to obtain the generated content with contentAsString (when writing to the response) or the redirected URL. These mocked versions of the Servlet API are completely mutable (unlike the real versions) and hence you can set properties on the request such as the contextPath and so on.Grails does not invoke interceptors or servlet filters when calling actions during integration testing. You should test interceptors and filters in isolation, using functional testing if necessary.Testing Controllers with Services
If your controller references a service (or other Spring beans), you have to explicitly initialise the service from your test.Given a controller using a service:class FilmStarsController {
def popularityService def update() {
// do something with popularityService
}
}class FilmStarsTests extends GroovyTestCase { def popularityService void testInjectedServiceInController () { def fsc = new FilmStarsController() fsc.popularityService = popularityService fsc.update() } }
Testing Controller Command Objects
With command objects you just supply parameters to the request and it will automatically do the command object work for you when you call your action with no parameters:Given a controller using a command object:class AuthenticationController {
def signup(SignupForm form) {
…
}
}def controller = new AuthenticationController() controller.params.login = "marcpalmer" controller.params.password = "secret" controller.params.passwordConfirm = "secret" controller.signup()
signup() as a call to the action and populates the command object from the mocked request parameters. During controller testing, the params are mutable with a mocked request supplied by Grails.Testing Controllers and the render Method
The render method lets you render a custom view at any point within the body of an action. For instance, consider the example below:def save() {
def book = Book(params)
if (book.save()) {
// handle
}
else {
render(view:"create", model:[book:book])
}
}modelAndView property of the controller. The modelAndView property is an instance of Spring MVC's ModelAndView class and you can use it to the test the result of an action:def bookController = new BookController()
bookController.save()
def model = bookController.modelAndView.model.bookSimulating Request Data
You can use the Spring MockHttpServletRequest to test an action that requires request data, for example a REST web service. For example consider this action which performs data binding from an incoming request:def create() {
[book: new Book(params.book)]
}void testCreateWithXML() { def controller = new BookController() controller.request.contentType = 'text/xml'
controller.request.content = '''\
<?xml version="1.0" encoding="ISO-8859-1"?>
<book>
<title>The Stand</title>
…
</book>
'''.stripIndent().getBytes() // note we need the bytes def model = controller.create()
assert model.book
assertEquals "The Stand", model.book.title
}void testCreateWithJSON() { def controller = new BookController() controller.request.contentType = "application/json"
controller.request.content =
'{"id":1,"class":"Book","title":"The Stand"}'.getBytes() def model = controller.create()
assert model.book
assertEquals "The Stand", model.book.title
}With JSON don't forget theFor more information on the subject of REST web services see the section on REST.classproperty to specify the name the target type to bind to. In XML this is implicit within the name of the<book>node, but this property is required as part of the JSON packet.
Testing Web Flows
Testing Web Flows requires a special test harness calledgrails.test.WebFlowTestCase which subclasses Spring Web Flow's AbstractFlowExecutionTests class.
Subclasses of WebFlowTestCase must be integration tests
For example given this simple flow:class ExampleController { def exampleFlow() {
start {
on("go") {
flow.hello = "world"
}.to "next"
}
next {
on("back").to "start"
on("go").to "subber"
}
subber {
subflow(action: "sub")
on("end").to("end")
}
end()
} def subFlow() {
subSubflowState {
subflow(controller: "other", action: "otherSub")
on("next").to("next")
}
…
}
}getFlow
method:import grails.test.WebFlowTestCaseclass ExampleFlowTests extends WebFlowTestCase { def getFlow() { new ExampleController().exampleFlow } … }
getFlowId method, otherwise the default is test:
import grails.test.WebFlowTestCaseclass ExampleFlowTests extends WebFlowTestCase { String getFlowId() { "example" } … }
protected void setUp() { super.setUp() registerFlow("other/otherSub") { // register a simplified mock start { on("next").to("end") } end() } // register the original subflow registerFlow("example/sub", new ExampleController().subFlow) }
startFlow method:void testExampleFlow() {
def viewSelection = startFlow()
…
}signalEvent method to trigger an event:void testExampleFlow() {
…
signalEvent("go")
assert "next" == flowExecution.activeSession.state.id
assert "world" == flowScope.hello
}hello variable into the flow scope.Testing Tag Libraries
Testing tag libraries is simple because when a tag is invoked as a method it returns its result as a string (technically aStreamCharBuffer but this class implements all of the methods of String). So for example if you have a tag library like this:class FooTagLib { def bar = { attrs, body ->
out << "<p>Hello World!</p>"
} def bodyTag = { attrs, body ->
out << "<${attrs.name}>"
out << body()
out << "</${attrs.name}>"
}
}class FooTagLibTests extends GroovyTestCase { void testBarTag() { assertEquals "<p>Hello World!</p>", new FooTagLib().bar(null, null).toString() } void testBodyTag() { assertEquals "<p>Hello World!</p>", new FooTagLib().bodyTag(name: "p") { "Hello World!" }.toString() } }
testBodyTag, we pass a block that returns the body of the tag. This is convenient to representing the body as a String.Testing Tag Libraries with GroovyPagesTestCase
In addition to doing simple testing of tag libraries like in the above examples, you can also use thegrails.test.GroovyPagesTestCase class to test tag libraries with integration tests.The GroovyPagesTestCase class is a subclass of the standard GroovyTestCase class and adds utility methods for testing the output of GSP rendering.
GroovyPagesTestCase can only be used in an integration test.
For example, consider this date formatting tag library:import java.text.SimpleDateFormatclass FormatTagLib { def dateFormat = { attrs, body -> out << new SimpleDateFormat(attrs.format) << attrs.date } }
class FormatTagLibTests extends GroovyPagesTestCase { void testDateFormat() { def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />' def testDate = … // create the date assertOutputEquals('01-01-2008', template, [myDate:testDate]) } }
applyTemplate method of the GroovyPagesTestCase class:class FormatTagLibTests extends GroovyPagesTestCase { void testDateFormat() { def template = '<g:dateFormat format="dd-MM-yyyy" date="${myDate}" />' def testDate = … // create the date def result = applyTemplate(template, [myDate:testDate]) assertEquals '01-01-2008', result } }
Testing Domain Classes
Testing domain classes is typically a simple matter of using the GORM API, but there are a few things to be aware of. Firstly, when testing queries you often need to "flush" to ensure the correct state has been persisted to the database. For example take the following example:void testQuery() {
def books = [
new Book(title: "The Stand"),
new Book(title: "The Shining")]
books*.save() assertEquals 2, Book.list().size()
}Book instances when called. Calling save only indicates to Hibernate that at some point in the future these instances should be persisted. To commit changes immediately you "flush" them:void testQuery() {
def books = [
new Book(title: "The Stand"),
new Book(title: "The Shining")]
books*.save(flush: true) assertEquals 2, Book.list().size()
}flush with a value of true the updates will be persisted immediately and hence will be available to the query later on.
12.3 Functional Testing
Functional tests involve making HTTP requests against the running application and verifying the resultant behaviour. The functional testing phase differs from the integration phase in that the Grails application is now listening and responding to actual HTTP requests. This is useful for end-to-end testing scenarios, such as making REST calls against a JSON API.Grails does not ship with any support for writing functional tests directly, but there are several plugins available for this.Canoo Webtest- http://grails.org/plugin/webtestG-Func- http://grails.org/plugin/functional-testGeb- http://grails.org/plugin/gebSelenium-RC- http://grails.org/plugin/selenium-rcWebDriver- http://grails.org/plugin/webdriver
Common Options
There are options that are common to all plugins that control how the Grails application is launched, if at all.inline
The-inline option specifies that the grails application should be started inline (i.e. like run-app).This option is implicitly set unless the baseUrl or war options are setwar
The-war option specifies that the grails application should be packaged as a war and started. This is useful as it tests your application in a production-like state, but it has a longer startup time than the -inline option. It also runs the war in a forked JVM, meaning that you cannot access any internal application objects.grails test-app functional: -war
https
The-https option results in the application being able to receive https requests as well as http requests. It is compatible with both the -inline and -war options.grails test-app functional: -https
-httpsBaseUrl option is also given.httpsBaseUrl
The-httpsBaseUrl causes the implicit base url to be used for tests to be a https url.grails test-app functional: -httpsBaseUrl
-baseUrl option is specified.baseUrl
ThebaseUrl option allows the base url for tests to be specified.grails test-app functional: -baseUrl=http://mycompany.com/grailsapp
-inline or -war are given as well. To use a custom base url but still test against the local Grails application you must specify one of either the -inline or -war options.

