Using mockFor() and HQL

In a previous post, we discussed how to actually go about combining mockFor() and mockDomain() when it comes to unit test support for .withCriteria. If your code uses the Gorm.createCriteria(), you’ll likely want to switch to .withCriteria to make it unit testable. We promised to cover using HQL as well so let’s do that now. Again, we’re assuming Grails 1.3.7.

The approach is unchanged from mocking criteria calls. We still need to use mockFor() to mock the static method usage. In our example, we’re using HQL because it’s easier to read and maintain than the equivalent criteria structure. Whatever your rationale, you should be able to follow along.

def defaultJobState = JobState.findAll(
    "from job_state where job_id = :jobId and default_state = :defaultState", 
    [jobId: jobId, defaultState : defaultState])
def orderedJobStates = JobState.findAll(
    "from job_state where job_id = :jobId and end_date >= :endDate order by effective_date", 
    [jobId :jobId, endDate : endDate])
... code for processing returned data

To make things interesting, our example shows two different HQL uses and named parameters. We’ll show how to differentiate between multiple uses and this can be applied to the criteria scenario as well. When we adopt our previous approach to HQL usage, we get the following unit test code.

    ...
    def testJobs = [defaultJobState, oldJobState, currentJobState]
    mockFor(JobState, testJobs)
    def mock = mockFor(JobState)
    mock.demand.static.findAll(1..5) { hqlString, params ->
        if (hqlString.contains("default_state")) {
            testJobs.findAll{ testJob ->
                testJob.defaultState == params.defaultState
            }
	} else if (hqlString.contains("end_date")) {
            testJobs.findAll{ testJob ->
                testJob.endDate >= params.endDate
            }
        } else {
            []
        }
    }

As you can see, we’re effectively implementing the equivalent to the HQL. We’re making some assumptions about our expected HQL usage – that we are only testing for two usages and that no other usages will be called or can default to no data and that no other uses, if called, will give false matches on our string checks. In your case, you would probably want to make a constant out of the HQL strings and then do exact matches against those values in your test. You might also want to enhance your use to throw an exception in your else block to protect against unexpected uses. Use your testing experience to make sure yours tests are correct and adequate.

Combining mockDomain() and mockFor() in Grails

As we’ve mentioned before, anything you can do to make automated testing easier in your Grails project will help you achieve one of the primary goals of the platform – high productivity.

Since you’ve chosen the Grails platform, you’re likely making good use of its features such as GORM, plugins, convention based spring wiring and Config.groovy/ConfigurationHolder. These all help you get to the business of writing your code without having to deal with plumbing all the time. Unfortunately the byproduct is that these all can make automated testing quite painful. Many times you end up going with integration tests simply because these give you access to the free plumbing even though you really just need to write unit tests. I won’t bother here debating and comparing unit vs. integration tests since this has been discussed many times before. We’ll just assume we want to write unit tests.

Grails (assuming 1.3.7) does provide the mockDomain(class, [instances]) which gives you an available “database” of objects in memory as provided to the mockDomain method without the need for a running container – perfect for unit tests. Unfortunately, as you start writing unit tests and encountering some issues, you come across this little gem in the documentation

… does not support the mocking of criteria or HQL queries

. That’s too bad really. You’d think they could easily support it, even if they just used an actual in memory database. Anyway, the documentation then adds

If you use either of those, simply mock the corresponding methods manually (for example with mockFor() ) or use an integration test with real data.

Sure, we could write an integration test, but we really don’t want to and shouldn’t have to. What about that mockFor() approach? Well you’re likely here because nobody has documented actually how to do so. We’re here to help. Here we’ll cover off combining mockDomain() with mockFor() for criteria in this post. In a future post, we’ll take a look at HQL queries.

It turns out to be not too bad. The use of closures leaves a little to be desired, but that’s not specific to this problem as it’s common to all mockFor(Domain).demand usage. Our example will assume you are using withCriteria to do something like find a range of data. We’ll also show you how you can provide other free behaviour via mockFor() – such as provided by a plugin.

We’ll have a domain class Order looking for orders placed within a specific date range and then provide mock behaviour for toDTO(dtoClass).

def order1 = new Order(orderId : 1, date : new Date(), amount : 19.99)
def order2 = new Order(orderId : 2, date : new Date(), amount : 39.99)
def order3 = new Order(orderId : 3, date : new Date(), amount : 49.99)
def order4 = new Order(orderId : 4, date : new Date(), amount : 99.99)
def orders = [order1, order2, order3, order4]
def mocker = mockFor(Order, true)
mocker.demand.toDTO(1..4) { clazz ->
    return new OrderDTO(orderId: delegate.orderId, date: delegate.date, amount: delegate.amount)
}
mocker.demand.static.withCriteria(1) {criteriaClosure ->
    def found = []
    orders.each { order ->
        if (criteriaClosure.from >= order.date && criteriaClosure <= order.date) {
            found << order
        }
    }
    return found
}
mockDomain(Order, [order1, order2, order3, order4])

Let's review what we've done. We create some test order instances for use both from our "database" and our mockFor / withCriteria logic. mockFor() gives us our handle to the mock that we can now set our expectations against since we're not really worried about testing against the mock and verifying its usage, we just need it to substitute for the database. toDTO is the free method you get with the DTO plugin. Use the mocker.demand as you would any grails mock. Then we substitute for the withCriteria code. Just make sure you use demand.static for withCriteria. We apply the logic against our collection of test instances (could just as easily have used the mocked instances available via mockDomain by doing Order.list().each) and now have a valid functioning "database" that supports criteria in unit tests.

Of course this doesn't actually test that your withCriteria is implemented correctly. The focus here was to establish an expected set of data to test against. You would have to write integration tests to actually test these types of methods on your model classes.

Testing in Grails – Common base class for Unit/Integration/Functional Tests

Groovy and even more so, Grails, demands automated testing. In fact, extensive coverage is a must to have confidence that we are not regressing or introducing runtime resolution issues with a single errant keystroke. Grails expects you to write tests and each grails project has the familiar test/unit, test/integration and test/functional source folders. Of course, we want to make it as easy as possible to write our tests. You already use a common base class for your grails tests, GrailsUnitTestCase. And it is a well-established practice in environments using testing frameworks such as jUnit to extend this approach to your own needs.

For example, it’s nice to be able to verify failure cases as we discussed in our last post. Perhaps you’re using a helper similar to the one previously described (shown below) in a base unit test class and want to extend its use to integration tests.

def expectThrown(Class expectedThrowable = Throwable, Closure closure) {
    try {
        closure()
    } catch(Throwable t) {
        if (!expectedThrowable.isInstance(t)) {
            throw t
        }
        return t
    }
    throw new AssertionError("Expected Throwable $expectedThrowable not thrown")
}

Well, if you have the base class in your test/unit source folder, it’s not available to test/integration and test/functional. You can place the base class src/groovy, but then this becomes a production class and is included in your .war file.

The current “solution” actually does start there. Place your base class in src/groovy. Then we want to add an exclusion in BuildConfig.groovy to ensure that this class – and any others you want excluded – are not packaged in the war.

BuildConfig.groovy

grails.war.resources = { stagingDir ->
    delete(file:"${stagingDir}/WEB-INF/classes/com/carfey/test/CarfeyGrailsTestCase.class")
    delete(dir:"${stagingDir}/WEB-INF/classes/com/carfey/test")
}

Tested against Grails 1.3.7.