15.1.1 Unit Testing Controllers - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.1.10
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' } }