Unit testing are tests at the "unit" level. In other words you are testing individual methods or blocks of code without considering for surrounding infrastructure. In Grails you need to be particularity aware of the difference between unit and integration tests because in unit tests Grails
does not inject any of the dynamic methods present during integration tests and at runtime.
This makes sense if you consider that the methods injected by Grails typically communicate with the database (with GORM) or the underlying Servlet engine (with Controllers).
For example say you have service like the following in
BookController
:
class MyService {
def otherService String createSomething() {
def stringId = otherService.newIdentifier()
def item = new Item(code: stringId, name: "Bangle")
item.save()
return stringId
} int countItems(String name) {
def items = Item.findAllByName(name)
return items.size()
}
}
As you can see the service takes advantage of GORM methods. So how do you go about testing the above code in a unit test? The answer can be found in Grails' testing support classes.
The Testing Framework
The core of the testing plugin is the
grails.test.GrailsUnitTestCase
class. This is a sub-class of
GroovyTestCase
geared towards Grails applications and their artifacts. It provides several methods for mocking particular types as well as support for general mocking a la Groovy's MockFor and StubFor classes.
Normally you might look at the
MyService
example shown previously and the dependency on another service and the use of dynamic domain class methods with a bit of a groan. You can use meta-class programming and the "map as object" idiom, but these can quickly get ugly. How might we write the test with GrailsUnitTestCase ?
import grails.test.GrailsUnitTestCaseclass MyServiceTests extends GrailsUnitTestCase {
void testCreateSomething() {
// Mock the domain class.
mockDomain(Item) // Mock the "other" service.
String testId = "NH-12347686"
def otherControl = mockFor(OtherService)
otherControl.demand.newIdentifier(1..1) {-> return testId } // Initialise the service and test the target method.
def testService = new MyService()
testService.otherService = otherControl.createMock() def retval = testService.createSomething() // Check that the method returns the identifier returned by the
// mock "other" service and also that a new Item instance has
// been saved.
def testInstances = Item.list()
assertEquals testId, retval
assertEquals 1, testInstances.size()
assertTrue testInstances[0] instanceof Item
} void testCountItems() {
// Mock the domain class, this time providing a list of test
// Item instances that can be searched.
def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
new Item(code: "EC-4395734", name: "Lamp"),
new Item(code: "TF-4927324", name: "Laptop") ]
mockDomain(Item, testInstances) // Initialise the service and test the target method. def testService = new MyService() assertEquals 2, testService.countItems("Laptop")
assertEquals 1, testService.countItems("Lamp")
assertEquals 0, testService.countItems("Chair")
}
}
OK, so a fair bit of new stuff there, but once we break it down you should quickly see how easy it is to use the methods available to you. Take a look at the "testCreateSomething()" test method. The first thing you will probably notice is the
mockDomain()
method, which is one of several provided by
GrailsUnitTestCase
:
def testInstances = []
mockDomain(Item, testInstances)
It adds all the common domain methods (both instance and static) to the given class so that any code using it sees it as a full-blown domain class. So for example, once the
Item
class has been mocked, we can safely call the
save()
method on instances of it. Invoking the
save()
method doesn't really save the instance to any database but it will cache the object in the testing framework so the instance will be visible to certain queries. The following code snippet demonstrates the effect of calling the
save()
method.
void testSomething() {
def testInstances=[]
mockDomain(Song, testInstances)
assertEquals(0, Song.count())
new Song(name:"Supper's Ready").save()
assertEquals(1, Song.count())
}
The next bit we want to look at is centered on the
mockFor
method:
def otherControl = mockFor(OtherService)
otherControl.demand.newIdentifier(1..1) {-> return testId }
This is analagous to the
MockFor
and
StubFor
classes that come with Groovy and it can be used to mock any class you want. In fact, the "demand" syntax is identical to that used by Mock/StubFor, so you should feel right at home. Of course you often need to inject a mock instance as a dependency, but that is pretty straight forward with the
createMock()
method, which you simply call on the mock control as shown. For those familiar with EasyMock, the name
otherControl
highlights the role of the object returned by
mockFor()
- it is a control object rather than the mock itself.
The rest of the
testCreateSomething()
method should be pretty familiar, particularly as you now know that the mock
save()
method adds instances to
testInstances
list. However, there is an important technique missing from the test method. We can determine that the mock
newIdentifier()
method is called because its return value has a direct impact on the result of the
createSomething()
method. But what if that weren't the case? How would we know whether it had been called or not? With Mock/StubFor the check would be performed at the end of the
use()
closure, but that's not available here. Instead, you can call
verify()
on the control object - in this case
otherControl
. This will perform the check and throw an assertion error if it hasn't been called when it should have been.
Lastly,
testCountItems()
in the example demonstrates another facet of the
mockDomain()
method:
def testInstances = [ new Item(code: "NH-4273997", name: "Laptop"),
new Item(code: "EC-4395734", name: "Lamp"),
new Item(code: "TF-4927324", name: "Laptop") ]
mockDomain(Item, testInstances)
It is normally quite fiddly to mock the dynamic finders manually, and you often have to set up different data sets for each invocation. On top of that, if you decide a different finder should be used then you have to update the tests to check for the new method! Thankfully the
mockDomain()
method provides a lightweight implementation of the dynamic finders backed by a list of domain instances. Simply provide the test data as the second argument of the method and the mock finders will just work.
GrailsUnitTestCase - the mock methods
You have already seen a couple of examples in the introduction of the
mock..()
methods provided by the
GrailsUnitTestCase
class. Here we will look at all the available methods in some detail, starting with the all-purpose
mockFor()
. But before we do, there is a very important point to make: using these methods ensures that any changes you make to the given classes do not leak into other tests! This is a common and serious problem when you try to perform the mocking yourself via meta-class programming, but that headache just disappears as long as you use at least one of
mock..()
methods on each class you want to mock.
mockFor(class, loose = false)
General purpose mocking that allows you to set up either strict or loose demands on a class.
This method is surprisingly intuitive to use. By default it will create a strict mock control object (one for which the order in which methods are called is important) that you can use to specify demands:
def strictControl = mockFor(MyService)
strictControl.demand.someMethod(0..2) { String arg1, int arg2 -> … }
strictControl.demand.static.aStaticMethod {-> … }
Notice that you can mock static methods as well as instance ones simply by using the "static" property after "demand". You then specify the name of the method that you want to mock with an optional range as its argument. This range determines how many times you expect the method to be called, so if the number of invocations falls outside of that range (either too few or too many) then an assertion error will be thrown. If no range is specified, a default of "1..1" is assumed, i.e. that the method must be called exactly once.
The last part of a demand is a closure representing the implementation of the mock method. The closure arguments should match the number and types of the mocked method, but otherwise you are free to add whatever you want in the body.
As we mentioned before, if you want an actual mock instance of the class that you are mocking, then you need to call
mockControl.createMock()
. In fact, you can call this as many times as you like to create as many mock instances as you need. And once you have executed the test method, you can call
mockControl.verify()
to check whether the expected methods were actually called or not.
Lastly, the call:
def looseControl = mockFor(MyService, true)
will create a mock control object that has only loose expectations, i.e. the order that methods are invoked does not matter.
mockDomain(class, testInstances = )
Takes a class and makes mock implementations of all the domain class methods (both instance- and static-level) accessible on it.
Mocking domain classes is one of the big wins from using the testing plugin. Manually doing it is fiddly at best, so it's great that mockDomain() takes that burden off your shoulders.
In effect,
mockDomain()
provides a lightweight version of domain classes in which the "database" is simply a list of domain instances held in memory. All the mocked methods (
save()
,
get()
,
findBy*()
, etc.) work against that list, generally behaving as you would expect them to. In addition to that, both the mocked
save()
and validate() methods will perform real validation (support for the unique constraint included!) and populate an errors object on the corresponding domain instance.
There isn't much else to say other than that the plugin does not support the mocking of criteria or HQL queries. If you use either of those, simply mock the corresponding methods manually (for example with
mockFor()
) or use an integration test with real data.
mockForConstraintsTests(class, testInstances = )
Highly specialised mocking for domain classes and command objects that allows you to check whether the constraints are behaving as you expect them to.
Do you test your domain constraints? If not, why not? If your answer is that they don't need testing, think again. Your constraints contain logic and that logic is highly susceptible to bugs - the kind of bugs that can be tricky to track down (particularly as save() doesn't throw an exception when it fails). If your answer is that it's too hard or fiddly, that is no longer an excuse. Enter the
mockForConstraintsTests()
method.
This is like a much reduced version of the
mockDomain()
method that simply adds a
validate()
method to a given domain class. All you have to do is mock the class, create an instance with field values, and then call
validate()
. You can then access the errors property on your domain instance to find out whether the validation failed or not. So if all we are doing is mocking the
validate()
method, why the optional list of test instances? That is so that we can test unique constraints as you will soon see.
So, suppose we have a simple domain class like so:
class Book {
String title
String author static constraints = {
title(blank: false, unique: true)
author(blank: false, minSize: 5)
}
}
Don't worry about whether the constraints are sensible or not (they're not!), they are for demonstration only. To test these constraints we can do the following:
class BookTests extends GrailsUnitTestCase {
void testConstraints() {
def existingBook = new Book(title: "Misery", author: "Stephen King")
mockForConstraintsTests(Book, [ existingBook ]) // Validation should fail if both properties are null.
def book = new Book()
assertFalse book.validate()
assertEquals "nullable", book.errors["title"]
assertEquals "nullable", book.errors["author"] // So let's demonstrate the unique and minSize constraints.
book = new Book(title: "Misery", author: "JK")
assertFalse book.validate()
assertEquals "unique", book.errors["title"]
assertEquals "minSize", book.errors["author"] // Validation should pass!
book = new Book(title: "The Shining", author: "Stephen King")
assertTrue book.validate()
}
}
You can probably look at that code and work out what's happening without any further explanation. The one thing we will explain is the way the errors property is used. First, it does return a real Spring
Errors
instance, so you can access all the properties and methods you would normally expect. Second, this particular
Errors
object also has map/property access as shown. Simply specify the name of the field you are interested in and the map/property access will return the name of the constraint that was violated. Note that it is the constraint name , not the message code (as you might expect).
That's it for testing constraints. One final thing we would like to say is that testing the constraints in this way catches a common error: typos in the "constraints" property! It is currently one of the hardest bugs to track down normally, and yet a unit test for your constraints will highlight the problem straight away.
mockLogging(class, enableDebug = false)
Adds a mock "log" property to a class. Any messages passed to the mock logger are echoed to the console.
mockController(class)
Adds mock versions of the dynamic controller properties and methods to the given class. This is typically used in conjunction with the
ControllerUnitTestCase
class.
mockTagLib(class)
Adds mock versions of the dynamic taglib properties and methods to the given class. This is typically used in conjunction with the
TagLibUnitTestCase
class.