Unit Testing Grails Controllers with Spock
Posted by jt - 25/11/11 at 10:11 amThe Spock testing framework has been creating a buzz in the Grails/Groovy community. And after attending a session on Spock at Spring One a couple weeks ago, I understand why. If Spock isn’t on your radar already, it should be.
Controllers have always been a pain to test; there’s just a lot of moving parts in a http request/response cycle to test. The Grails framework comes out of the box with some great mocks to support unit testing of controllers. The Spock plugin builds upon these to make your controller unit tests feel – well, more ‘unit-y’.
This week I wrote a controller method to provide a JSON response for a logged in user’s profile.
Here is the controller we are dealing with:
class ProfileController { def userService // return a json object of the users data def getUserProfile() { def returnMap = [:] returnMap.success = false def user = userService.getLoggedinUser() if (user) { try { returnMap.username = user.username returnMap.firstName = user.firstName returnMap.lastName = user.lastName returnMap.fbUrl = user.fbUrl returnMap.fbPhotoUrl = user.fbPhotoUrl returnMap.primaryEmail = user.primaryEmail?.email returnMap.primaryPhone = user.primaryPhone?.phoneNumber returnMap.sms = user.sms?.phoneNumber if (user?.emails) { returnMap.emails = [] user.emails.each { returnMap.emails << [id: it.id, email: it.email] } } if (user?.phoneNumbers) { returnMap.phoneNumbers = [] user.phoneNumbers.each { returnMap.phoneNumbers << [id: it.id, phoneNumber: it.phoneNumber, phoneType: it.phoneType] } } returnMap.success = true } catch (e) { log.error(e) log.error(e.message) returnMap.message = e.message } } else { returnMap.success = false returnMap.message = 'User Not Found' } render returnMap as JSON // return the map as a JSON object to the web request } } |
The controller method calls an injected service (userService) to get the user object, then builds a response map & renders it as JSON. Yes, I could have just rendered the user object as JSON, but in this case I did not want to. There are a number of properties I did not want to expose through the API. Also, I wanted to set a success and message property for the API consumer.
Now to test this guy, I need to mock out the userService to return a user object, build the controller, inject the mock into the controller, call the controller method, and finally verify the JSON response. Piece of cake, right?
One of the things I like about Spock is its given, when, then DSL. I’ve found when writing unit tests it helps me organize what I want to do. Ok, given is the stuff I need to setup. When is the do something part. And then is what I want to check.
Ok for the given part, the first thing I need to do is create a user account for the mock to return. In this example, I’m creating the properties I want to verify later as final variables which I’ll use in the assert later.
//user object for testing def final USERNAME = "billyjoejimbob" //user object for testing def user = new User() user.username = USERNAME |
Next step is to make a mock object for the userService. While Spock comes out of the box with some really nice mocking capabilities, I don’t need anything too complex here. I’m just going to use a little applied Groovage for my mock object. Here in this snippet, I create a map & assign a closure to a map property using the same name as the method my controller will invoke. This will function nicely as my mock to return the user object I want to use for testing.
//build mock user service def userService = [:] userService.getLoggedinUser = {return user} |
One of the nice things about testing controllers with the Spock pluggin for Grails is it follows a convention to automatically wire up your controller for testing. A Spock class with the name of SomeControllerSpec, will automatically have a ‘controller’ property wired up as a SomeController using the typical Grails mocks. I like this little feature since it helps keep the ceremonial fluff out of your code. Thus, injecting the mock into the controller becomes easy as:
//inject into controller controller.userService = userService |
For the when part of the test, I need to call the controller method, then get the response. The response is automagically captured to a ‘mockReponse’ object. In this case, I know my response is JSON. Now, one might be tempted to test the JSON string, but that would have a really bad code smell. I’m more interested in the properties of the JSON object, than I am in the overall string value. So I’m going to slurp that JSON response into a Groovy object.
//execute controller method controller.getUserProfile() //parse controller response to object def jsonSlurper = new JsonSlurper() def responseObj = jsonSlurper.parseText(mockResponse.contentAsString) |
Now to check the values. One of the cool things about Spock, the then clause of the DSL just looks for statements that evaluate to true. No need to write a bunch of ‘assert’ statements. In this case, I want to test the success property and that my username was set correctly.
responseObj.success responseObj.username == USERNAME |
One Spock feature that I didn’t point out was the naming of the test methods. You can write your method names in actual text, which works out nicely for your test reports. Makes the reports FAR more readable than using names like ThisIsSomeUnitTestForWhateverIWantedToTest.
Putting it all together, my test looks like:
def "Test JSON response of Get User Profile with minimal user object"() { given: //user object for testing def final USERNAME = "billyjoejimbob" //user object for testing def user = new User() user.username = USERNAME //build mock user service def userService = [:] userService.getLoggedinUser = {return user} //inject into controller controller.userService = userService when: //execute controller method controller.getUserProfile() //parse controller response to object def jsonSlurper = new JsonSlurper() def responseObj = jsonSlurper.parseText(mockResponse.contentAsString) then: responseObj.success responseObj.username == USERNAME } |
Since SpringOne, Spock has become my testing drug of choice. Although, I’m still learning its features, I found I was quickly up to speed and productive on the tool.
A bunch of random technology stuff that has my attention. I work with a lot of Oracle, Java, and dabble with various open source software packages.
November 28th, 2011 at 1:30 pm
This is a great post! Thanks…
One suggestion only, you can put a string in the given, when and then blocks for make more readable test code instead of using comments…
January 10th, 2013 at 8:11 pm
Thanks
Great post guy! It was VERY helpful to me.
I still learning Spock and Grails, but came to like him very much.
March 1st, 2013 at 2:27 pm
Thank you for this post, I was looking for how to test JSON response and that was just perfect, so thank you!