The Missing Infrastructure
Developing with Grails is a real delight, and testing my delightful Grails applications with Spock makes the experience even better.
Some of Grails’ test cases, such as the Controllers, require a specialized setup because you don’t test them in the same manner as you would test PO[GJ]Os; you must mock requests and such.
When testing the REST resources provided by the excellent JAX-RS plugin, the situation is no different; we can test the resources only through specialized cases where a proper environment has been set up.
Thankfully, the authors of the JAX-RS plugin have already provided us with great infrastructure for integration testing with JUnit.
But alas, testing with Spock requires specialized classes because Spock uses a different inheritance hierarchy, and uses different methods for setup/tear-down and sharing data between specifications; so some work is required.
The Solution
Future Solution
The good news is that I’ve already prepared a pull request that adds this functionality, and it was merged in to the project fairly quickly (thanks Martin!).
Current Solution
So how do we get by until the next version of the plugin is released? I’ll now show you how easy it is to add a temporary Spock test infrastructure to your project.
We’ll simply clone the existing JUnit integration test base and adapt it to use the facilities provided by Spock.
1. Dependencies
First and foremost, let’s add a dependency on the latest Grails Spock plugin.
We’ll edit our project’s BuildConfig.groovy (which is located at $PROJECT_HOME/grails-app/conf/BuildConfig.groovy) and add the plugin dependency configuration:
grails.project.dependency.resolution = {
plugins {
test ":spock:0.7"
}
}
Note that the grails.project.dependency.resolution and plugins configuration hierarchies might already exist in the file.
2. The Base Test Specification
Once our project has been reloaded and the dependencies have been resolved, we can create our base test specification.
Let’s create a new Groovy class named RestApiIntegrationSpec within our project’s integration test folder ($PROJECT_HOME/test/integration) and add the following content:
package org._10ne.example.interaction.test
import grails.plugin.spock.IntegrationSpec
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.grails.jaxrs.JaxrsController
import org.grails.jaxrs.itest.IntegrationTestEnvironment
import org.grails.jaxrs.web.JaxrsContext
import org.grails.jaxrs.web.JaxrsUtils
import spock.lang.Shared
import javax.servlet.http.HttpServletResponse
/**
* @author Noam Y. Tenne
*/
class RestApiIntegrationSpec extends IntegrationSpec {
@Shared
IntegrationTestEnvironment testEnvironment
GrailsApplication grailsApplication
def controller
@Shared
def headers = ['Content-Type': 'application/json', 'Accept': 'application/json']
def setupSpec() {
testEnvironment = null
}
def setup() {
grailsApplication.config.org.grails.jaxrs.dowriter.require.generic.collections = false
grailsApplication.config.org.grails.jaxrs.doreader.disable = false
grailsApplication.config.org.grails.jaxrs.dowriter.disable = false
if (!testEnvironment) {
testEnvironment = new IntegrationTestEnvironment(contextLocations, jaxrsImplementation, jaxrsClasses, autoDetectJaxrsClasses)
}
controller = new JaxrsController()
controller.jaxrsContext = testEnvironment.jaxrsContext
}
void setRequestUrl(String url) {
JaxrsUtils.setRequestUriAttribute(controller.request, url)
}
void setRequestMethod(String method) {
controller.request.method = method
}
void setRequestContent(byte[] content) {
controller.request.content = content
}
void addRequestHeader(String key, Object value) {
controller.request.addHeader(key, value)
}
void resetResponse() {
controller.response.committed = false
controller.response.reset()
}
HttpServletResponse getResponse() {
controller.response
}
HttpServletResponse sendRequest(String url, String method, byte[] content = ''.bytes) {
sendRequest(url, method, [:], content)
}
HttpServletResponse sendRequest(String url, String method, Map<String, Object> headers, byte[] content = ''.bytes) {
resetResponse()
requestUrl = url
requestMethod = method
requestContent = content
headers.each { entry ->
addRequestHeader(entry.key, entry.value)
}
controller.handle()
controller.response
}
/**
* Implementors can define additional Spring application context locations.
*/
String getContextLocations() {
''
}
/**
* Returns the JAX-RS implementation to use. Default is 'jersey'.
*/
String getJaxrsImplementation() {
JaxrsContext.JAXRS_PROVIDER_NAME_JERSEY
}
/**
* Returns the list of JAX-RS classes for testing. Auto-detected classes
* will be added to this list later.
*/
List getJaxrsClasses() {
[]
}
/**
* Determines whether JAX-RS resources or providers are auto-detected in
* <code>grails-app/resources</code> or <code>grails-app/providers</code>.
*
* @return true is JAX-RS classes should be auto-detected.
*/
boolean isAutoDetectJaxrsClasses() {
true
}
}
You’ll notice that the changes we made to the original class have been fairly subtle:
- The file ends with “Spec” instead of “Test” – This way we adhere to Spock’s naming conventions which are depended upon the by the different build and test report frameworks.
- The class extends grails.plugin.spock.IntegrationSpec – This will provide us with the different Grails facilities required for the tests.
- Variables that are shared between specifications are annotated with @Shared instead of being made static.
- Procedures that must be run once before the whole test’s executed are called from the setupSpec method.
- Procedures that must be run once before the every specification are called from the setup method.
3. The Tests
Finally, to write our tests, all we must do is extend our RestApiIntegrationSpec class!
Have fun testing!