Grails, test, educational technologies

Grails Unit Testing: covering domain classes and services

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Product {
   String code
   String name

   static constraints = {
       code blank: false, unique: true
       name blank: false, minSize: 3
   }

   static mapping = {
       table 'products'
       version false
   }
}
1
2
3
4
5
6
7
8
9
10
class ProductService {

   def saveProduct(String code, String name) {
       new Product(code: code, name: name).save()
   }

   def productCount() {
       Product.count()
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
   }
}

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()
   }

   // ...
}

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:

1
2
3
4
5
6
class HelperService {

   def generateCode() {
       // method implementation
   }
}
1
2
3
4
5
6
7
8
9
10
class ProductService {

   def helperService

   def saveProduct(String name) {
       new Product(code: helperService.generateCode(), name: name).save()
   }

   // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
   }

   // ...
}

Here, the generateCode()'s behaviour is replaced using the demand property of the control object:

1
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:

1
2
3
4
5
6
7
8
class ProductService {

   def logSomething() {
       log.info "log text"
   }

   // ...
}
1
2
3
4
5
6
7
8
9
class ProductServiceTests extends GrailsUnitTestCase {

   void testLogging() {
       mockLogging(ProductService, true)
       new ProductService().logSomething()
   }

   // ...
}

This code results in the following output:

1
INFO (examples.ProductService): log text

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.

Looking to hire a software developer?
Don't hesitate to contact us.

Comments