Unit testing in Grails is currently implemented with Grails Testing plugin. The plugin provides unit testing facilities through the GrailsUnitTestCase
class which offers a lot of useful methods for mocking Grails-specific objects. This post demonstrates how some of these methods can be used to test Grails domain classes and services.
The post is written for the Grails version 1.3.7
Domain classes & mockDomain
The GrailsUnitTestCase.mockDomain
is designed to replace implementations of the domain class methods. For instance, it adds new save()
, get()
and delete()
implementations which do not persist an object state to a database, instead they keep the object in the testing framework cache:
class Product {
String code
String name
static constraints = {
code blank: false, unique: true
name blank: false, minSize: 3
}
static mapping = {
table 'products'
version false
}
}
Code language: JavaScript (javascript)
class ProductService {
def saveProduct(String code, String name) {
new Product(code: code, name: name).save()
}
def productCount() {
Product.count()
}
}
Code language: JavaScript (javascript)
class ProductServiceTests extends GrailsUnitTestCase {
void testSaveProduct() {
mockDomain(Product)
assertEquals Product.list().size(), 0
new ProductService().saveProduct("code_0", "product_0")
assertEquals Product.list().size(), 1
}
void testProductCount() {
def testInstances = [
new Product(code: "code_1", name: "product_1"),
new Product(code: "code_2", name: "product_2"),
new Product(code: "code_3", name: "product_3")]
mockDomain(Product, testInstances)
def productService = new ProductService()
assertEquals productService.productCount(), 3L
productService.saveProduct("code_0", "product_0")
assertEquals productService.productCount(), 4L
}
}
Code language: JavaScript (javascript)
As you can see, the mockDomain(Product, testInstances)
adds three product instances to the cache, so they can be retrieved later with the get()
, or count()
. Also, since the Product
domain is mocked, the cache will be updated instead of a database when the save()
method is called from the productService.saveProduct()
.
Constraints & mockForConstraintsTest
The GrailsUnitTestCase.mockForConstraintsTest
is another tool for testing domain classes, as you have already guessed, this method should be used to test constraint declarations. The code below must be self-explanatory:
class ProductTests extends GrailsUnitTestCase {
void testConstraints() {
mockForConstraintsTests(Product, [new Product(code: "code", name: "product")])
def product = new Product()
assertFalse product.validate()
assertEquals "nullable", product.errors["code"]
assertEquals "nullable", product.errors["name"]
product = new Product(code: "code", name: "pr")
assertFalse product.validate()
assertEquals "unique", product.errors["code"]
assertEquals "minSize", product.errors["name"]
product = new Product(code: "code", name: "product")
assertTrue product.validate()
}
// ...
}
Code language: JavaScript (javascript)
Services & mockFor
The GrailsUnitTestCase.mockFor
came from Groovy, it is an all-purpose method that allows to create a mock for any class. This makes it a perfect tool for replacing service class implementations:
class HelperService {
def generateCode() {
// method implementation
}
}
Code language: JavaScript (javascript)
class ProductService {
def helperService
def saveProduct(String name) {
new Product(code: helperService.generateCode(), name: name).save()
}
// ...
}
Code language: JavaScript (javascript)
class ProductServiceTests extends GrailsUnitTestCase {
void testSaveProductCodeGenerated() {
mockDomain(Product)
String code = "code_1"
String name = "product_1"
def mockControl = mockFor(HelperService)
mockControl.demand.generateCode(1..1) {-> return code }
def productService = new ProductService()
productService.helperService = mockControl.createMock()
productService.saveProduct(name)
assertEquals Product.list().size(), 1
assertTrue Product.list()[0] instanceof Product
assertEquals Product.findByCode(code).name, name
}
// ...
}
Code language: JavaScript (javascript)
Here, the generateCode()
‘s behaviour is replaced using the demand
property of the control object:
mockControl.demand.foo(1..2) {-> ...}
where:
- mockControl – the control object
- demand – starts a new expectation
- foo – the name of the expected method
- (1..2) – the number of calls. If no range is specified, the default of “1..1” will be assumed
- {-> …} – closure that provides a new method implementation
Logging & mockLogging
The testing plugin also allows to intercept log messages and redirect them to the output stream. This is done with the GrailsUnitTestCase.mockLogging
method which replaces a log
property of a class:
class ProductService {
def logSomething() {
log.info "log text"
}
// ...
}
Code language: JavaScript (javascript)
class ProductServiceTests extends GrailsUnitTestCase {
void testLogging() {
mockLogging(ProductService, true)
new ProductService().logSomething()
}
// ...
}
Code language: JavaScript (javascript)
This code results in the following output:
INFO (examples.ProductService): log text
Code language: CSS (css)
Conclusion
The rumors are that Grails 2.0 will provide mixin-based implementations of the testing facilities, which, of course, would be much more appropriate for the framework. In the meantime, the GrailsUnitTestCase
already allows to create perfectly fine, though a bit verbose, unit tests for the objects that are specific for Grails.