Groovys .with() and multiple assignment
July 9th, 2014
Groovy has a limitation that restricts multiple assignment to simple variables. However, by using with(), we may be able to work around that.
You’ve written (or are about to write) an API in Grails. Which is quite easy, especially with all the REST API improvements in Grails 2.3. Now you want to ensure your API works correctly when the first users start hitting it with JSON. But you don’t want to write and maintain raw JSON strings - what are some existing libraries you could use to help make testing your API easier?
Let’s walk through some quick examples of testing a Grails API with the Grails Rest Client Builder plugin, the Groovy Http Builder library, and the Apache Fluent Http Client library. We’ll use JSON in these examples, but you could easily substitute XML if your API supports that format.
Let’s start with a simple Grails API for a Person class with a first and last name. We’ll use the Grails 2.3 @Resource annotation to quickly create the API from the domain class:
import grails.rest.Resource
@Resource(uri = '/person', formats = ['json'])
class Person implements Serializable {
String firstName
String lastName
static constraints = {
firstName(nullable: false)
lastName(nullable: false)
}
}
If your project doesn’t already have a plugin that enables functional testing (e.g. Geb, Webdriver, etc.), then you’ll need to add code similar to this to your scripts/_Events.groovy file to enable the functional test phase. I borrowed this code from the Functional Test plugin:
// If you have another plugin that enables functional tests,
// such as Geb, you won't need to do this
eventAllTestsStart = {
if (getBinding().variables.containsKey("functionalTests")) {
functionalTests << "functional"
}
}
If you do have a plugin that enables the functional test phase, don’t add this to your _Events.groovy file. Otherwise your functional tests may run twice.
The Grails Rest Client Builder plugin is built to call Rest APIs from your application code, but it can easily be used to call APIs in your test as well. Here is a quick example of using a GET to retrieve a person:
def 'should fetch person with Grails REST client builder'() {
given:
Person person = personRemoteControl.createPerson([firstName: 'Rest', lastName: 'Smith'])
RestBuilder rest = new RestBuilder()
when:
RestResponse response = rest.get("http://localhost:8080/grails-api-testing/person/${person.id}") {
// Need to set the accept content-type to JSON, otherwise it defaults to String
// and the API will throw a 415 'unsupported media type' error
accept JSON
}
then:
assert response.status == 200
assert response.json.firstName == person.firstName
assert response.json.lastName == person.lastName
}
One thing to note when issuing a GET with the REST client builder plugin - I had to set the ‘accept’ type to JSON. Otherwise the plugin defaulted to accepting the String type and the API complained that String was an unsupported type and threw a 415 error.
And using a POST to create a new Person:
def 'should create a person with Grails REST client builder'() {
given:
String newFirstName = "RestNew"
String newLastName = "Smith"
RestBuilder rest = new RestBuilder()
when:
def response = rest.post("http://localhost:8080/grails-api-testing/person") {
json {
firstName = newFirstName
lastName = newLastName
}
}
then:
assert response.status == 201
assert personRemoteControl.findByFirstName(newFirstName)?.lastName == newLastName
}
The Groovy Http Builder library provides a handy syntax to call APIs over Http. In these examples we’ll use the RESTClient subset of the Http Builder library since it is geared specifically towards calling Rest APIs and provides a more concise syntax for doing so.
First, we’ll issue a GET to fetch an existing Person:
def 'should fetch a person with Groovy Http-Builder'() {
given:
Person person = personRemoteControl.createPerson([firstName: 'Httpbuilder', lastName: 'Smith'])
RESTClient restClient = new RESTClient("http://localhost:8080/grails-api-testing/")
when:
def response = restClient.get(path: "person/${person.id}")
then:
assert response.status == 200
assert response.data.firstName == person.firstName
assert response.data.lastName == person.lastName
}
Then we’ll POST a new Person to our API:
def 'should create a person with Groovy Http-Builder'() {
given:
String newFirstName = "HttpNew"
String newLastName = "Smith"
RESTClient restClient = new RESTClient("http://localhost:8080/grails-api-testing/")
when:
def response = restClient.post(
path: "person",
body: [firstName: newFirstName, lastName: newLastName],
contentType: groovyx.net.http.ContentType.JSON
)
then:
assert response.status == 201
assert personRemoteControl.findByFirstName(newFirstName)?.lastName == newLastName
}
The Apache Http Client library is a popular, well-worn Java library that dates back over a decade, but it is still widely used. We’ll use it’s Fluent version for its powerful and readable builder-like syntax.
We’ll start with a GET to grab an existing Person:
def 'should fetch a person with Apache Http Client Fluent'() {
given:
Person person = personRemoteControl.createPerson([firstName: 'Httpclient', lastName: 'Smith'])
when:
Content responseContent = Request.Get("http://localhost:8080/grails-api-testing/person/${person.id}")
.execute()
.returnContent()
def json = JSON.parse(responseContent.toString())
then:
assert json.firstName == person.firstName
assert json.lastName == person.lastName
}
Then use a POST to create a new Person:
def 'should create a new person with Apache Http Client Fluent'() {
given:
String newFirstName = "ApacheNew"
String newLastName = "Smith"
String jsonString = new JSON(firstName: newFirstName, lastName: newLastName)
when:
Request.Post("http://localhost:8080/grails-api-testing/person")
.bodyString(jsonString, org.apache.http.entity.ContentType.APPLICATION_JSON)
.execute()
.returnContent()
then:
assert personRemoteControl.findByFirstName(newFirstName)?.lastName == newLastName
}
I also wrote tests for updating and deleting a Person with PUT and DELETE, those tests are available on GitHub: PUT test and DELETE test
I hope these examples will give you a starting place to see what options are available to test your Grails API. And check out the full source code for this testing project on GitHub: https://github.com/craigatk/grails-api-testing
Happy testing!
Groovy has a limitation that restricts multiple assignment to simple variables. However, by using with(), we may be able to work around that.
Review of material and presentations from Gradle Summit 2014, Santa Clara, California.
Inline initialization of Java Maps is never pretty, this solution provides a type safe and efficient way to do so.
Insert bio here