Showing posts with label unit testing. Show all posts
Showing posts with label unit testing. Show all posts

Thursday, 25 September 2008

Effective testing on Grails

Gus and I will be presenting on "Effective Testing on Grails" at the London Groovy and Grails User Group next week Wed 1st of October, @18.30. Please feel free to attend if this seems relevant to you. A synopsis of the talk and registration for the event are all available at the following link.

http://skillsmatter.com/event/java-jee/ggug

Thursday, 28 August 2008

Unit Testing Controllers With The Testing Plugin

Grails JUnit tests are, as you are no doubt aware, divided into unit tests and integration tests. Unit tests run fast, aren't dependent on the database or Spring context but mean you have to live without all the dynamic magic that Grails attaches to your artefacts (controllers, taglibs, domain classes, etc.) This can certainly lead to some sub-optimal test code.

Lets consider an example. I want to write some test coverage for a controller that looks up a domain object based on a parameter and places it in the model. Usual disclaimer: of course, in the real world I'd have written the test first, etc. Nothing much to the controller:
class LolrusController {
def show = {
def myLolrus = Lolrus.findByName(params.lolrusName)
render(view: 'lolrus', model: [lolrus: myLolrus])
}
}
and here's the domain class:
class Lolrus {
String name
String mood
static hasMany = [posessions: Item]
static constraints = {
name(unique: true)
posessions(validator: { posessions ->
return posessions.any { it.type == 'bukkit' }
})
}
String toString() { "Lolrus[$name]" }
boolean equals(Object o) { return o instanceof Lolrus && o.name == name }
int hashCode() { return 37 * name.hashCode() }
}
Okay, so a reasonable test (ignoring edge cases for now) could be to create a couple of domain objects, make a request to the controller and verify that the correct domain object is retrieved. Simple, right?:
class LolrusControllerTests extends GroovyTestCase {
def controller
def lolrus1, lolrus2

void setUp() {
controller = new LolrusController()

lolrus1 = new Lolrus(name: 'Hugh')
lolrus2 = new Lolrus(name: 'Alan')
[lolrus1, lolrus2]*.save(flush: true)
}

void testShowActionFindsCorrectLolrusAndSticksItInModel() {
controller.params.lolrusName = lolrus1.name
def model = controller.show()
assertEquals('/lolrus/show', controller.modelAndView.viewName)
assertEquals(lolrus1, controller.modelAndView.model.lolrus)
}
}
Unfortunately, not so simple. The test fails and reports
expected:<Lolrus[Hugh]> but was:<null>
What we've forgotten of course is that Lolrus has a mandatory field mood and a custom validation on its posessions association that requires it to have a bukkit. The two Lolrus instances we tried to create in setUp couldn't be saved. Great. Well, let's just add that to the test set up:
    void setUp() {
controller = new LolrusController()

lolrus1 = new Lolrus(name: 'Hugh', mood: 'worryingly cheerful')
lolrus1.addToPosessions new Item(type: 'bukkit')

lolrus2 = new Lolrus(name: 'Alan', mood: 'mildly distressed')
lolrus2.addToPosessions new Item(type: 'bukkit')

[lolrus1, lolrus2].each {
assert it.save(flush: true), it.errors
}
}
Okay, it works but look how much of what's going on in setUp is actually nothing to do with anything the test cares about. This is a very simple example with a single test case. Imagine how much worse that setUp method is going to get when we add more tests or the Lolrus domain object gets more complex... Imagine the refactoring required when the Lolrus domain object acquires other constraints... Imagine the query is more complex requiring more domain objects to be set up to really prove it works...

Okay, we could mock the Lolrus class, but the code for that isn't much cleaner and alarm bells start ringing when you start using mocks in an integration test. Shouldn't this be a unit test? Sure, that would be nice but we're using the render dynamic method in our controller, we'll have to mock that out on the metaClass, mock out the findByName method on Lolrus, etc. etc. Too much hassle right? Not any more.
grails install-plugin testing
Allows us to (among other things) write controller unit test cases like this:
class LolrusControllerUnitTests extends grails.test.ControllerUnitTestCase {
def controller
def lolrus1, lolrus2

void setUp() {
super.setUp()
controller = new LolrusController()

lolrus1 = new Lolrus(name: 'Hugh')
lolrus2 = new Lolrus(name: 'Alan')
mockDomain(Lolrus, [lolrus1, lolrus2])
}

void testShowActionFindsCorrectLolrusAndSticksItInModel() {
controller.params.lolrusName = lolrus1.name
controller.show()
assertEquals('show', renderArgs.view)
assertEquals(lolrus1, renderArgs.model.lolrus)
}
}
Much neater. We're not worrying about the constraints required to set up valid Lolrus instances, but neither are we having to mock out the findByName method.

The mockDomain method allows you to set up some domain objects for which all the usual Grails domain class dynamic methods will work - including findBy..., addTo..., etc. The collection passed as the second argument to mockDomain defines all the instances that exist and they can be retrieved just as a real domain object could. The mockDomain method co-exists happily with regular Groovy mocks and the testing plugin also provides some lighter mocking capabilities via its own mockFor method.

Additionally the controller's render method and params property work without any work on our part. This is true of all the dynamic properties and methods of the controller, request, response, params, controllerName, redirect, etc.

Not only that, but this test runs faster than the integration test as it's not configuring the Spring container or an in-memory database. That speed difference only grows as the project becomes more complex - it's a couple of seconds between the two tests here, but in a more complex project it's a lot more.

There are a couple of gotchas (the plugin isn't fully mature). First off you need to remember to call super.setUp() in order that all the Grails stuff happens. The ControllerUnitTestCase class will figure out based on the class naming convention which controller class it needs to mock up. Secondly, the domain class get method doesn't seem to work with non-standard identifier types at the moment (i.e. anything other than a Long). However, this unit testing support is intended to get rolled into Grails 1.1 so it should improve rapidly.

This is only part of what the testing plugin can do. Support for unit testing domain class constraints, etc. is also included. I've also been working on a TagLibUnitTestCase that works along the same lines as ControllerUnitTestCase.

Tuesday, 12 August 2008

Overriding constraints

I didn't know you could override constraints until the other day. Here is an example of overriding and then testing the constraints with the new testing plugin. Testing constraints with the new plugin is so easy I suggest every team uses it. We started using it for our team. Here's the code:

class Parent {
String name
String body

static constraints = {
name(blank: false, nullable:false)
body(blank: false, nullable:false)
}
}

class Child extends Parent{
static constraints = {
name(blank: true)
body(blank: true)
}
}


------------Tests using the testing plugin ---------------
import grails.test.GrailsUnitTestCase
import grails.test.MockUtils

class ChildTests extends GrailsUnitTestCase{

void testConstraints() {
// Mock the validate() method.
registerMetaClass(Child)
MockUtils.prepareForConstraintsTests(Child)

registerMetaClass(Parent)
MockUtils.prepareForConstraintsTests(Parent)

def testInstance = new Child()
def errors = testInstance.validate()
assertEquals 0, errors.size()

// Test the parent constraints
testInstance = new Parent(name:' ' )
errors = testInstance.validate()
assertEquals 2, errors.size()
assertEquals "blank", errors["name"]
assertEquals "nullable", errors["body"]
}
}

Happy testing :)

Wednesday, 2 July 2008

Good Unit Testing Practice

A common but misguided practice that is often seen in unit test designs, is to test some kind of a false moniker on an object, rather than the actual properties of the object that should be tested.
For example. We have a simple "Dog" object and we want to get a list of all Dogs of a certain breed,ordered by the date of birth of the dog.



class Dog{
Breed breed;
Date dob;
String name;

}


When testing using this moniker style we might test like this (notice that i have introduced errors but the tests still pass since they are testing the moniker and not the properties that should be tested)...


void testGetAllAlsationsOrderedByAgeUsingMonikers(){

Dog fido = new Dog(name:'alsation born last year', dob: '01/01/2006', breed:Breed.ALSATION)
Dog woofer = new Dog(name:'alsation born 2 years ago', dob:'01/01/2008',breed:Breed.LABRADOR)
Dog mutley = new Dog(name:'alsation born this year', dob: '01/01/2008', breed:Breed.ALSATION)

List dogs = DogService.getDogByBreed(Breed.ALSATION)
assertTrue 'alsation born 2 years ago', dogs[0].name
assertTrue 'alsation born born last year', dogs[1].name
assertTrue 'alsation born this year', dogs[2].name

}


the problem with this is that we are actually not testing for the real properties that we should be testing for, and we are relying on the fact that we didn't introduce any errors in our test set up. which, incidentally,we did, just to prove the point!


A much better way to test this would be the following...



void testGetAllAlsationsOrderedByAgeCorrectly(){

Dog fido = new Dog(name:'not relevant', dob: '01/01/2006', breed:Breed.ALSATION)
Dog woofer = new Dog(name:'not relevant', dob: '01/01/2008', breed:Breed.LABRADOR)
Dog mutley = new Dog(name:'not relevant', dob: '01/01/2008', breed:Breed.ALSATION)

List dogs = DogService.getDogByBreed(Breed.ALSATION)
assertEquals(2, dogs.size())
assertTrue( isOrderedByDateOfBirth( dogs) )
assertTrue ( dogs.every{ Dog dog -> dog.breed=Breed.ALSATION} )

}

private boolean isOrderedByDateOfBirth( List dogs) {

def dobComparator = [compare:{Dog dog1, Dog dog2 ->
dog1.dob == dob2.dob ? 0 :dog1.dob > dog2.dob? 1 : -1 }] as Comparator
return dogs.sort(dobComparator) == dogs


}