Grails Domain Mocking Limitations


So, I just threw out most of this morning trying to figure out why something which clearly should work was blowing up my unit test on a grails app. To spare you the same pain I’m documenting it here.

The scenario is that I have Roles and Privileges as domain classes. A role has many privileges, and any privilege may belong to one or more roles. This is represented in domain classes pretty succinctly as;

/grails-app/domain/com/nslms/mockdomainlimtations/Role.groovy

package com.nslms.mockdomainlimitations
class Role {
  static hasMany = [privileges: Privilege]
  static constraints = {
  }
  String name
}

/grails-app/domain/com/nslms/mockdomainlimtations/Privilege.groovy

package com.nslms.mockdomainlimitations
class Privilege {
  static constraints = {
  }
  String name
}

So I can access all the privileges which belong to a role pretty easily, but what if I want to know all roles which a particular privilege belongs to? Easy enough, we can look that up in a variety of ways. Below I show adding a new closure to the privilege domain class which uses the withCriteria functionality of GORM to return all roles which have this privilege in the privileges map. The new closure is in the highlighted lines.

/grails-app/domain/com/nslms/mockdomainlimtations/Privilege.groovy (with new closure)

package com.nslms.mockdomainlimitations
class Privilege {
  static constraints = {
  }
  String name
  def getRolesWithThisPrivilege = {
    Role.withCriteria() {
      privileges {
        eq('id', this.id)
      }
    }
  }
}

Now, if you’re doing proper test driven development (you are doing TDD, right?!), you’d probably already have a test written for this new closure that would look something like the highlighted lines of the test fixture below. Notice lines 8 and 9 which are also highlighted to show that we’re asking the framework to mock out the GORM methods on the role and privilege classes.

/test/unit/com/nslms/mockdomainlimitaions/PrivilegeTests.groovy

package com.nslms.mockdomainlimitations
import grails.test.*
class PrivilegeTests extends GrailsUnitTestCase {
  protected void setUp() {
    super.setUp()
    MockUtils.mockDomain(Role.class)
    MockUtils.mockDomain(Privilege.class)
  }
  protected void tearDown() {
    super.tearDown()
  }
  void testAbilityToGetAListOfRolesAPrivilegeBelongsTo() {
    def role1 = new Role(name: 'Administrator')
    def role2 = new Role(name: 'User')
    def priv1 = new Privilege(name: 'ReadAll').save(flush: true)
    role1.addToPrivileges(priv1)
    role2.addToPrivileges(priv1)
    role1.save(flush: true)
    role2.save(flush: true)
    def roleList = priv1.getRolesWithThisPrivilege()
    assert roleList.size() == 2
    assert roleList == [role1, role2]
  }
}

Even after you’ve implemented getRolesWithThisPrivilege on the Privileges domain class though, you’ll find your test still fails with an error that looks like the following.

No signature of method: com.nslms.mockdomainlimitations.Role.withCriteria() is applicable for argument types: (com.nslms.mockdomainlimitations.Privilege$_closure1_closure3) values: [com.nslms.mockdomainlimitations.Privilege$_closure1_closure3@8327473]

In short, it’s telling us that the withCriteria method of GORM isn’t implemented in the context of our test. Of course if you put the exact same code in an integration test you’re golden.

/test/integration/com/nslms/mockdomainlimitations/PrivilegeTest.groovy

package com.nslms.mockdomainlimitations
class PrivilegeTest extends GroovyTestCase {
  void testAbilityToGetAListOfRolesAPrivilegeBelongsTo() {
    def role1 = new Role(name: 'Administrator')
    def role2 = new Role(name: 'User')
    def priv1 = new Privilege(name: 'ReadAll').save(flush: true)
    role1.addToPrivileges(priv1)
    role2.addToPrivileges(priv1)
    role1.save(flush: true)
    role2.save(flush: true)
    def roleList = priv1.getRolesWithThisPrivilege()
    assert roleList.size() == 2
    assert roleList == [role1, role2]
  }
}

With this in place, you can run a “grails test-app -integration” and the exact same test which failed during unit testing will succeed. This is of course because the entire grails bootstrapping occurs, and all of the artifacts (like domain classes) are wired up fully by the framework.

So the moral of the story? If you’re planning to test anything more than simple saves with GORM in your testing phase, consider putting the more complex stuff into an integration test. Either that, or keep your eyes peeled for problems like this and be prepared to refactor.

Feel free to grab a copy of the test grails app I created for this example.

svn export https://linode.nslms.com/svn_ro/MockDomainLimitations

* UPDATE: This example app has a new home..

Grab the project

git clone git://ec2.nslms.com/grails/blog_example_mock_limitations

, , , , ,

  • http://burtbeckwith.com/blog/ Burt Beckwith

    You shouldn’t use mocking to test your domain classes. You’re just testing the mocking framework, and it doesn’t support all of the features of GORM. Use mocking when testing controllers or services so you can isolate what you’re testing and not deal with unrelated functionality. Always use integration tests for domain classes, except possibly when testing utility methods that don’t depend on GORM or the database.

  • Joshua Davis

    I like your example, but it says very clearly in the reference manual that you can’t use it for criteria queries:
    http://grails.org/doc/latest/guide/9.%20Testing.html

  • http://www.nslms.com RyanG

    Quite right Joshua, but obviously something I missed when getting up to speed with testing in Grails.

    I figure since it’s only mentioned in passing it’s likely others will miss it as well, and hopefully if they go searching, they might find this post.

    Just sharing my experience and the stuff I wasted time on! :-)

  • Anna Skawińska

    Just as Joshua said, it’s there in the manual. What’s not there, however, is that apparently the testing framework doesn’t propagate save() in one-to-many relationships, even with flush set to true. Just spent an evening trying to figure it out :)
    Another reason to agree with Burt.

  • http://www.nslms.com RyanG

    Anna, I can’t think of a lot of reasons not to agree with Burt. ;-)