Mastering Grails: Mock testing with Grails

Unit testing for speed

In this Mastering Grails installment, Scott Davis shows you how to take advantage of the built-in mocking capabilities of the GrailsUnitTestCase and ControllerUnitTestCase classes included with Grails.

In this article, you'll learn how the mocking capabilities of GrailsUnitTestCase and ControllerUnitTestCase make it easy to unit test Grails artifacts that you'd otherwise assume need to be tested using an integration test. The mockForConstraintsTests(), mockDomain(), and mockLogging() methods all leverage Groovy's metaprogramming magic to make testing your domain classes, services, and controllers a breeze.

In "Testing your Grails application," you learned about unit and integration tests:

About this series

Grails is a modern Web development framework that mixes familiar Java™ technologies like Spring and Hibernate with contemporary practices like convention over configuration. Written in Groovy, Grails give you seamless integration with your legacy Java code while adding the flexibility and dynamism of a scripting language. After you learn Grails, you'll never look at Web development the same way again.

Grails supports two basic types of tests: unit and integration. There's no syntactical difference between the two: both are written as a GroovyTestCase using the same assertions. The difference is the semantics. A unit test is meant to test the class in isolation, whereas the integration test allows you to test the class in a full, running environment.

That article was written with Grails 1.0 — the current release at the time — in mind. With the subsequent release of Grails 1.1, the testing infrastructure received a major boost in functionality. The introduction of the GrailsUnitTestCase class and its children brings a whole new level of testing ease and sophistication to the process. Specifically, the mocking capabilities of these new test classes allow you gain the speed of a unit test while testing functionality normally found only in an integration test. Figure 1 shows the new testing hierarchy in Grails 1.1.x:

Figure 1. The new testing hierarchy in Grails 1.1.x
The new testing hierarchy in Grails 1.1.x

When you create a new domain class and controller in the next section, you'll see a GrailsUnitTestCase and a ControllerUnitTestCase in action. (Full source code for the article's examples are available for download.)

Getting started

To follow along with the examples in this article, start by creating a new application. At the command prompt, type:

grails create-app testing

Change to the testing directory (cd testing), then type:

grails create-domain-class User

And then:

grails create-controller User

Add the code in Listing 1 to grails-app/domain/User.groovy:

Listing 1. The User domain class
class User {
  String name
  String login
  String password
  String role = "user"

  static constraints = {
    name(blank:false)
    login(unique:true, blank:false)
    password(password:true, minSize:5)
    role(inList:["user", "admin"])
  }

  String toString(){
    "${name} (${role})"
  }
}

Scaffold out the core behavior of grails-app/controller/UserController.groovy, as shown in Listing 2:

Listing 2. The UserController class
class UserController {
    def scaffold = true
}

Now that the basic infrastructure is in place, it's time to add some tests.


Mocking in GrailsUnitTestCase

Open test/unit/UserTests.groovy in a text editor. The code is shown in Listing 3:

Listing 3. The UserTests class
import grails.test.*

class UserTests extends GrailsUnitTestCase {
    protected void setUp() {
        super.setUp()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testSomething() {

    }
}

In Grails 1.0, the stubbed-out test created for you by the create-domain-class command extended GroovyTestCase. As you can see, the unit test for a domain class now (with Grails 1.1) extends GrailsUnitTestCase. Consequently, you have some new methods available that enable you to mock out functionality in a unit test that previously would have required an integration test.

Specifically, a GrailsUnitTestCase offers the following mock methods:

  • mockForConstraintsTests()
  • mockDomain()
  • mockLogging()

To understand how valuable these mock methods are, start by creating a test that will intentionally fail. Change the testSomething() method to the testBlank() method, as shown in Listing 4:

Listing 4. A test that will fail
void testBlank() {
  def user = new User()
  assertFalse user.validate()
}

You may be asking yourself why this test will fail. After all, it is valid syntactically. The answer is that you are running a unit test. Unit tests are meant to run in isolation, which means that no database is running, no Web server is running, and — most important — no Grails-related metaprogramming occurs.

Looking back at the source code of the User domain class in Listing 1, clearly no validate() method is defined. This method (along with save(), list(), hasErrors(), and all of the other Groovy Object Relational Mapping (GORM) methods you are familiar with) gets dynamically added to the domain class by Grails at run time.

To run the failing test, type grails test-app at the command prompt. You should see the results shown in Listing 5:

Listing 5. Failing test in console output
$ grails test-app
Environment set to test

Starting unit tests ...
Running tests of type 'unit'
-------------------------------------------------------
Running 2 unit tests...
Running test UserControllerTests...PASSED
Running test UserTests...
                    testBlank...FAILED
Tests Completed in 1434ms ...
-------------------------------------------------------
Tests passed: 1
Tests failed: 1
-------------------------------------------------------

Starting integration tests ...
Running tests of type 'integration'
No tests found in test/integration to execute ...

Tests FAILED - view reports in /testing/test/reports.

Before you view the failing report, did you notice how quickly the unit tests ran, as well as the noticeable delay in running the integration tests? Type grails test-app -unit to run just the unit tests. Even though the test is still failing, you should see a marked improvement in the speed of the test run.

You can, of course, type grails test-app -integration to run just the integration tests. As a matter of fact, you can even combine the unit and integration flags with the name of the test class. Type grails test-app -unit User to target the specific test class you are interested in. (Notice that you drop the Tests suffix from the end of the name — less typing is always a good thing.) Being able to focus your test narrowly run to a single class can make a dramatic difference in how motivated you'll be to write tests in the real world.

Knowing that you have a failing test, you probably want to see the error message. Open test/reports/html/index.html in a Web browser. Click through to the failing test class. The results are shown in Figure 2:

Figure 2. Report showing the failing unit test
Report showing the failing unit test

The No signature of method: User.validate() error message confirms that Grails, indeed, hasn't metaprogrammed the validate() method onto the User class.

At this point, you have two options. The first option is to move this test class into the integration directory. But seeing how long it takes Grails to spin up to run the integration tests, this option is less than appealing. The second option is to mock out the validation behavior and leave the test in the unit directory.


Understanding mockForConstraintsTests()

To mock out the Grails validation in a unit test, add the mockForConstraintsTests() method, as shown in Listing 6. This method instructs Grails to metaprogram the validation methods onto the specified domain class as it would normally during run time.

Listing 6. A test that will pass, thanks to mockForConstraintsTests()
void testBlank() {
  mockForConstraintsTests(User)
  def user = new User()
  assertFalse user.validate()
}

Now, run the test to verify that it passes, as shown in Listing 7:

Listing 7. Running the passing test
$ grails test-app -unit User
Environment set to test

Starting unit tests ...
Running tests of type 'unit'
-------------------------------------------------------
Running 1 unit test...
Running test UserTests...PASSED
Tests Completed in 635ms ...
-------------------------------------------------------
Tests passed: 1
Tests failed: 0
-------------------------------------------------------

Tests PASSED - view reports in /testing/test/reports.

To refine your unit test further, you can assert that the validation failed for a specific constraint on a specific field, as shown in Listing 8. The mockForConstraintsTests() method metaprograms an errors collection onto the domain class. This errors collection makes it easy to verify that the proper constraint was triggered.

Listing 8. Asserting a specific constraint violation on a specific field
void testBlank() {
  mockForConstraintsTests(User)
  def user = new User()
  assertFalse user.validate()

  println "=" * 20
  println "Total number of errors:"
  println user.errors.errorCount

  println "=" * 20
  println "Here are all of the errors:"
  println user.errors

  println "=" * 20
  println "Here are the errors individually:"
  user.errors.allErrors.each{
    println it
    println "-" * 20
  }

  assertEquals "blank", user.errors["name"]
}

Rerun this test. Did it fail unexpectedly? Look at the report output, shown in Figure 3, to figure out the root cause:

Figure 3. Failing caused by nullable instead of blank
Failing caused by nullable instead of blank

The error message is expected:<[blank]> but was:<[nullable]>. So validation failed, but not for the reason you might have expected it to.

It's easy to get caught by this error. By default, all fields in a domain class are required to be nonnull in Grails. The trouble with this implicit constraint is that you usually interact with Grails via an HTML form. If you leave a String field blank in an HTML form, it comes back to the controller in the paramsMap as an empty String (that is, "") instead of null.

If you click on the System.out link at the bottom of the HTML report, you can see that three of the String fields (name, login, and password) all threw the nullable constraint violation. Figure 4 shows the output from the println calls. Only the role field — the one that had a default value of user — passed the implicit nullable constraint.

Figure 4. The System.out output of the test
The System.out output of the test

Adjust the testBlank() test one more time to ensure that validation fails (and therefore, the unit test passes) for the proper reason, as shown in Listing 9:

Listing 9. Test that now passes for the right reasons
void testBlank() {
  mockForConstraintsTests(User)
  def user = new User(name:"",
                      login:"admin",
                      password:"wordpass")
  assertFalse user.validate()
  assertEquals 1, user.errors.errorCount
  assertEquals "blank", user.errors["name"]
}

Once you've rerun the test to ensure that it passes, it's time to tackle a slightly more tricky constraint: unique.


Testing the unique constraint with mockForConstraintsTests()

As you saw in the preceding section, testing for most constraints is easy to do in isolation. For example, testing that the minSize of the password field is at least 5 is easy because it doesn't rely on anything except the value of the field itself. Listing 10 shows the testPassword() method:

Listing 10. Testing the minSize constraint
void testPassword() {
  mockForConstraintsTests(User)
  def user = new User(password:"foo")
  assertFalse user.validate()
  assertEquals "minSize", user.errors["password"]
}

But how do you test a constraint like unique, which ensures that the database table contains no duplicate values? Thankfully, mockForConstraintsTests() takes a second argument: a list of domain classes used to mock out (act as a stand-in for) the real database table. Listing 11 demonstrates testing the unique constraint with a mocked-out table:

Listing 11. Testing the unique constraint with a mocked-out table
void testUniqueLogin(){
  def jdoe = new User(name:"John Doe",
                      login:"jdoe",
                      password:"password")

  def suziq = new User(name:"Suzi Q",
                       login:"suziq",
                       password:"wordpass")

  mockForConstraintsTests(User, [jdoe, suziq])

  def jane = new User(login:"jdoe")
  assertFalse jane.validate()
  assertEquals "unique", jane.errors["login"]
}

The ability to mock out a database table in-memory is a huge time saver, especially when you consider how long it takes to start up an actual database. To add insult to injury, once the database is up and running, you are still faced with the challenge of ensuring that the database table is prepopulated with the records necessary for your assertions to pass.

I'm not suggesting that running true integration tests against your production database is a waste of time. I'm suggesting that those long-running integration tests are better suited for a continuous-integration server. In this case, mocking out the database interaction allows you to focus on the Grails functionality, doing it in a fraction of the time it would otherwise take.

The ability to mock out database tables extends beyond the mockForConstraintsTests() method. You can do the same thing with the mockDomain() method.


Understanding mockDomain()

GORM metaprograms a number of useful methods onto domain classes: save(), list(), and a whole host of dynamic finders such as findAllByRole(). As the name implies, the mockForConstraintsTests() method adds the validation methods back onto the domain class for testing purposes. The mockDomain() method adds the persistence methods back onto the domain class for testing purposes. Listing 12 shows the mockDomain() method in action:

Listing 12. Using mockDomain() to test GORM methods
void testMockDomain(){
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")
  def jsmith = new User(name:"Jane Smith", role:"user")

  mockDomain(User, [jdoe, suziq, jsmith])

  //dynamic finder
  def list = User.findAllByRole("admin")
  assertEquals 1, list.size()

  //NOTE: criteria, Hibernate Query Language (HQL)
  //      and Query By Example (QBE) are not supported
}

The mockDomain() method models GORM behavior as faithfully as possible. For example, when you save a domain class to a mock table, the id field is populated as it would be in a live application. The id value is simply the ordinal value of the element in the list. Listing 13 demonstrates saving a domain class in a unit test:

Listing 13. Saving a domain class in a unit test
void testMockGorm(){
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")
  def jsmith = new User(name:"Jane Smith", role:"user")

  mockDomain(User, [jdoe, suziq, jsmith])

  def foo = new User(login:"foo")
  foo.name = "Bubba"
  foo.role = "user"
  foo.password = "password"
  foo.save()
  assertEquals 4, foo.id  //NOTE: id gets assigned
  assertEquals 3, User.findAllByRole("user").size()
}

The limitations of mocking and metaprogramming

The mockDomain() method is simply leveraging the native dynamic capabilities of the underlying Groovy language. (To learn more about metaprogramming in Groovy, see "Practically Groovy: Metaprogramming with closures, ExpandoMetaClass, and categories.") In effect, it is intercepting the method calls that would normally exist on the domain class and replacing them with mocked-out behavior for testing purposes. This means that, not surprisingly, other supporting technology — such as criteria blocks, the Hibernate Query Language (HQL), and Query By Example (QBE) — are not mocked out. If your code relies on any of these technologies, you need to write integration tests and have an actual database up and running.

Mocking out the underlying database isn't the only thing that you can do in a GrailsUnitTestCase. You can also mock out the logging infrastructure.


Understanding mockLogging()

A GrailsUnitTestCase is useful for more than just testing domain classes. Create an Admin service, as shown in Listing 14, by typing grails create-service Admin:

Listing 14. Creating a service
$ grails create-service Admin

Created Service for Admin
Created Tests for Admin

Not surprisingly, the AdminService.groovy file appears in the grails-app/services directory. If you look in the test/unit directory, you should see a GrailsUnitTestCase named AdminServiceTests.groovy.

Add a hypothetical method to AdminService that only allows users in the admin role to restart the server, as shown in Listing 15:

Listing 15. Adding a restart() method to AdminService
class AdminService {
  boolean transactional = true

  def restartServer(User user) {
    if(user.role == "admin"){
      //restart the server
      return true
    }else{
      log.info "Ha! ${user.name} thinks s/he is an admin..."
      return false
    }
  }
}

Testing this service should be pretty straightforward, right? Add a testRestartServer() method to test/unit/AdminServiceTests.groovy, as shown in Listing 16:

Listing 16. A service test that will fail
void testRestartServer() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  //NOTE: no DI in unit tests
  def adminService = new AdminService()
  assertTrue adminService.restartServer(suziq)
  assertFalse adminService.restartServer(jdoe)
}

When you run this test by typing grails test-app -unit AdminService at the command prompt, it fails. Just like your initial User test runs, it fails for reasons you might not expect initially. Looking at the HTML report, the familiar No such property: log for class: AdminService message haunts you, as shown in Figure 5:

Figure 5. Lack of dependency injection causing unit-test failures
Lack of dependency injection causing unit-test failures

This time, however, the failure isn't due to the lack of metaprogramming on a domain class. It's due to the lack of dependency injection. Specifically, all Grails artifacts get a log object injected into them at run time so that they can easily log messages for later review.

To inject a mock logger for testing purposes, wrap the AdminService class in a mockLogging() method call, as shown in Listing 17:

Listing 17. Service test that will pass, thanks to mockLogging()
void testRestartServer() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  mockLogging(AdminService)
  def adminService = new AdminService()
  assertTrue adminService.restartServer(suziq)
  assertFalse adminService.restartServer(jdoe)
}

This time, the test passes as expected. And any log output is sent to System.out. Remember that you can see this output in the HTML reports.


Understanding ControllerUnitTestCase

You can easily test domain classes and services with a GrailsUnitTestCase, but testing a controller requires some additional functionality. A ControllerUnitTestCase extends GrailsUnitTestCase, so you can still use mockForConstraintsTests(), mockDomain(), and mockLogging() as you did before. Also, a ControllerUnitTestCase creates a new instance of the controller you are testing and stores it in the aptly named controller variable. This controller variable is how you programmatically interact with the controller during the test.

To visualize the core controller functionality better, type grails generate-controller User at the command prompt. This replaces def scaffold = true with the full implementation of the controller code.

In the fully implemented grails-app/controllers/UserController.groovy file, you can see that calling the index action redirects you to the list action, as shown in Listing 18:

Listing 18. The default index action in UserController
class UserController {

    def index = { redirect(action:list,params:params) }

}

To verify that the redirect happens as expected, add a testIndex() method to test/unit/UserControllerTests.groovy, as shown in Listing 19:

Listing 19. Testing the default index action
import grails.test.*

class UserControllerTests extends ControllerUnitTestCase {
    void testIndex() {
      controller.index()
      assertEquals controller.list, controller.redirectArgs["action"]
    }
}

As you can see, you first invoke the controller action as if it were a method call on the controller. The redirect arguments are stored in a Map named redirectArgs. The assertion verifies that the action key contains the list value. (If the action ends with a render, you can assert against the Map named renderArgs.)

Suppose now that the index action is slightly more advanced. It checks the session for a User and redirects based on whether that user is an admin or not. In a ControllerUnitTestCase, both session and flash are Maps that you can populate before the call or assert against after the call. Change the index action as shown in Listing 20:

Listing 20. A more advanced index action
def index = {
  if(session?.user?.role == "admin"){
    redirect(action:list,params:params)
  }else{
    flash.message = "Sorry, you are not authorized to view this list."
    redirect(controller:"home", action:index)
  }
}

To test your new functionality, change the testIndex() method in UserControllerTests.groovy, as shown in Listing 21:

Listing 21. Testing for session and flash values
void testIndex() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  controller.session.user = jdoe
  controller.index()
  assertEquals "home", controller.redirectArgs["controller"]
  assertTrue controller.flash.message.startsWith("Sorry")

  controller.session.user = suziq
  controller.index()
  assertEquals controller.list, controller.redirectArgs["action"]
}

Some controller actions expect incoming parameters. In a ControllerUnitTestCase, you can add values to the params Map just as you did to flash and session. Listing 22 shows the default show action:

Listing 22. The default show action
def show = {
    def userInstance = User.get( params.id )

    if(!userInstance) {
        flash.message = "User not found with id ${params.id}"
        redirect(action:list)
    }
    else { return [ userInstance : userInstance ] }
}

Remember the mockDomain() method from GrailsUnitTestCase? You can use it here to mock out the User table, as shown in Listing 23:

Listing 23. Testing the default show action
void testShow() {
  def jdoe = new User(name:"John Doe",
                      login:"jdoe",
                      password:"password",
                      role:"user")

  def suziq = new User(name:"Suzi Q",
                      login:"suziq",
                      password:"wordpass",
                      role:"admin")

  mockDomain(User, [jdoe, suziq])

  controller.params.id = 2

  // this is the HashMap returned by the show action
  def returnMap = controller.show()
  assertEquals "Suzi Q", returnMap.userInstance.name
}

Testing RESTful Web services with ControllerUnitTestCase

Sometimes, to test your controller, you need access to the raw request and response. In the case of a ControllerUnitTestCase, these are exposed to you via the controller.request and controller.response objects: GrailsMockHttpServletRequest and GrailsMockHttpServletResponse, respectively.

You can review "Mastering Grails: RESTful Grails" for a guide to setting up the RESTful services. Combine that with "Practically Groovy: Building, parsing, and slurping XML" to parse the results, and you have everything you need to test your RESTful Web services.

Add a simple listXml action to UserController, as shown in Listing 24. (Don't forget to import the grails.converters package.)

Listing 24. Simple XML output in the controller
import grails.converters.*
class UserController {
  def listXml = {
    render User.list() as XML
  }

  // snip...
}

Then add a testListXml() method to UserControllerTests.groovy, as shown in Listing 25:

Listing 25. Testing the XML output
void testListXml() {

  def suziq = new User(name:"Suzi Q",
                      login:"suziq",
                      password:"wordpass",
                      role:"admin")

  mockDomain(User, [suziq])

  controller.listXml()
  def xml = controller.response.contentAsString
  def list = new XmlParser().parseText(xml)
  assertEquals "suziq", list.user.login.text()

  //output
  /*
  <?xml version="1.0" encoding="UTF-8"?>
  <list>
    <user>
      <class>User</class>
      <id>1</id>
      <login>suziq</login>
      <name>Suzi Q</name>
      <password>wordpass</password>
      <role>admin</role>
      <version />
    </user>
  </list>
  */
}

The first thing that happens in this test is that you create a new User and store it in the suziq variable. Next, you mock out the User table, storing suziq as the only record.

With this preliminary setup in place, you next call the listXml() action. To get the resulting XML from the action as a String, you call controller.response.contentAsString and store it in the xml variable.

At this point, you have a raw String. (The content of this String is shown for reference purposes in the output comment at the end of the method.) Calling new XmlParser().parseText(xml) returns the root element (<list>) as a groovy.util.Node object. Once you have the root node of the XML document, you can use a GPath expression (for example, list.user.login.text()) to assert that the <login> element contains the expected value (in this case, suziq).

As you can see, the Grails converters package makes it easy to produce the XML, the native Groovy library XmlParser makes it easy to parse the XML, and the ControllerUnitTestCase makes it easy for you to test the resulting GrailsMockHttpServletResponse. That's a remarkably powerful combination of technologies that takes remarkably few lines of code to test.


Conclusion

In this article, you learned how the baked-in test classes — GrailsUnitTestCase and ControllerUnitTestCase— make testing your Grails application a snap. The mockForConstraintsTests(), mockDomain(), and mockLogging() methods all dramatically speed you along by allowing you to write fast unit tests instead of considerably slower integration tests.

Next time, I'll walk through a few of the community-driven testing plug-ins that make integration tests less painful. Until then, have fun mastering Grails.


Download

DescriptionNameSize
Source codej-grails10209.zip164KB

Resources

Learn

Get products and technologies

  • Grails: Download the latest Grails release.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=438556
ArticleTitle=Mastering Grails: Mock testing with Grails
publish-date=10202009