Fork me on GitHub

1 Jan 2010

Using GMock to complement Grails mockDomain

Since Grails 1.1 we've had pretty good unit testing support via GrailsUnitTestCase and its sub-classes. The mockDomain method is particularly useful for simulating the various enhancements Grails adds to domain classes. However, there are some domain class capabilities, such as criteria queries and the new named queries, that can't really be simulated by mockDomain.

So assuming we're trying to unit test a controller that uses criteria methods or named queries on a domain class how can we enhance the capabilities of mockDomain? One of my favourite Groovy libraries is GMock which I use in preference to Groovy's built in mock capabilities. One of its really powerful features is the ability to use 'partial mocks', i.e. to mock particular methods on a class whilst allowing the rest of the class to continue functioning as normal. This means we can layer a mocked createCriteria, withCriteria or named query call on to a domain class that is already enhanced by mockDomain.

First off you need to add the GMock dependency to your BuildConfig.groovy. Since GMock supports Hamcrest matchers for matching method arguments you'll probably want those as well:
    dependencies {
        test "org.gmock:gmock:0.8.0"
        test "org.hamcrest:hamcrest-all:1.0"
    }
If you're using an earlier version of Grails you'll need to just grab the jar files and put them in your app's lib directory.

Then in your test case you need to import GMock and Hamcrest classes and add an annotation to allow GMock to work:
    import grails.test.*
    import org.gmock.*
    import static org.hamcrest.Matchers.*

    @WithGMock
    class MyControllerTests extends ControllerUnitTestCase {

Adding criteria and named query methods is now fairly simple:

Mocking a withCriteria method

    def results = // whatever you want your criteria query to return
    mock(MyDomain).static.withCriteria(instanceOf(Closure)).returns(results)
    play {
        controller.myAction()
    }
Breaking this example down a little
  1. mock(MyDomain) establishes a partial mock of the domain class.
  2. instanceOf(Closure) uses a Hamcrest instanceOf matcher to assert that the withCriteria method is called with a single Closure argument (the bit with all the criteria in).
  3. returns(results) tells the mock to return the specified results which here would be a list of domain object instances.
In this example we're expecting the withCriteria method to be called just once but GMock supports more complex time matching expressions if the method may be called again.

Mocking a createCriteria method

The withCriteria method returns results directly but createCriteria is a little more complicated in that it returns a criteria object that has methods such as list, count and get. To simulate this we'll need to have the mocked createCriteria method return a mocked criteria object.
    def results = // whatever you want your criteria query to return
    def mockCriteria = mock() {
        list(instanceOf(Closure)).returns(results)
    }
    mock(MyDomain).static.createCriteria().returns(mockCriteria)
    play {
        controller.myAction()
    }
This is only a little more complex than the previous example in that it has the mocked list method on another mock object that is returned by the domain class' createCriteria method. mock() provides an un-typed mock object as we really don't care about the type here.

Mocking a named query

For our purposes named queries are actually pretty similar to createCriteria.
    def results = // whatever you want your criteria query to return
    def mockCriteria = mock() {
        list(instanceOf(Closure)).returns(results)
    }
    mock(MyDomain).static.myNamedQuery().returns(mockCriteria)
    play {
        controller.myAction()
    }

Some other examples:

Mocking a named query with an argument

    mock(MyDomain).static.myNamedQuery("blah").returns(mockCriteria)
For simple parameters you don't need to use a Hamcrest matcher - a literal is just fine.

Mocking a withCriteria call using options

You can pass an argument map to withCriteria, e.g. withCriteria(uniqueResult: true) { /* criteria */ } will return a single instance rather than a List. To mock this you will need to expect the Map as well as the Closure:
    def result = // a single domain object instance
    mock(MyDomain).static.withCriteria(uniqueResult: true, instanceOf(Closure)).returns(result)

Mocking criteria that are re-used

It's fairly common in pagination scenarios to call a list and count method on a criteria object. We can just set multiple expectations on the mock criteria object, e.g.
    def mockCriteria = mock() {
        list(max: 10).returns(results)
        count().returns(999)
    }
    mock(MyDomain).static.myNamedQuery().returns(mockCriteria)

The nice thing about this technique is that it doesn't interfere with any of the enhancements mockDomain makes to the domain class, so the save, validate, etc. methods will still work as will dynamic finders.

Be aware however, that what we're doing here is mocking out the criteria queries, not testing them! All the interesting stuff inside the criteria closure is being ignored by the mocks and could, of course, be garbage. Named queries are pretty easy to test by having integration test cases for your domain class. Criteria queries beyond a trivial level of complexity should really be encapsulated in service methods or named queries and integration tested there. Of course, GMock then makes an excellent solution for mocking that service method in your controller unit test.

29 comments:

Johnny Jian said...

Good to see a blog introducing GMock. :)

Actually, "mock(MyDomain).static..." is called static mocking, and partial mocking is something like "mock(controller)...". Nevertheless, static mocking is also mocking static methods partially, so you can call it "static partial mocking". :)

There is a new feature called chained methods in the incoming GMock 0.9.0 can simplify the mocking the createCriteria(). You can chains the methods like "mock(MyDomain).static.createCriteria().chains().list(...).returns(results)".

What's more, you can also test the closure of withCriteria() in GMock 0.9.0 like:
mock(MyDomain).static.withCriteria(invoke {
between(...)
like(...)
}).returns(results)

Rob said...

Nice, I didn't know about that feature.

Chaining will be really useful as well.

Steve Dalton said...

Awesome post Rob, just what I needed!

Unknown said...

Rob, I'm trying to follow your examples, mainly with the named queries, but in my case to test the named query itself.

So I have something like (sorry for my spanish coding):

def results = [] << Historico.findByProgramaID('2') << Historico.findByProgramaID('3') << Historico.findByProgramaID('4')

def mockCriteria = mock() {
list(instanceOf(Closure)).returns(results)
}

mock(Historico).static.todosActivos().returns(mockCriteria)

And then when I try to play it, I'm getting an error:

play {
assert results == Historico.todosActivos()
}

Could it be even possible to do something like this?:

play {
def r = Historico.todosActivos()
assert ['2', '3', '4'] == r*.programaID

Thanks in advance.

Johnny Jian said...

I think it should be

assert results == Historico.todosActivos().list {...}

Unknown said...

Unfortunately I get this error when trying the "Historico.todosActivos().list { ... }:

nexpected method call 'list()' on 'Mock for Object' 'list(an instance of groovy.lang.Closure)' on 'Mock for Object': expected 1, actual 0 'Historico.todosActivos()': expected 1, actual 1

junit.framework.AssertionFailedError: Unexpected method call 'list()' on 'Mock for Object'
'list(an instance of groovy.lang.Closure)' on 'Mock for Object': expected 1, actual 0
'Historico.todosActivos()': expected 1, actual 1
at org.gmock.internal.InternalMockController.fail(InternalMockController.groovy:185)
at org.gmock.internal.InternalMockController$fail.call(Unknown Source)
at org.gmock.internal.metaclass.MetaClassHelper.findExpectation(MetaClassHelper.groovy:25)
at org.gmock.internal.metaclass.MetaClassHelper$findExpectation.callStatic(Unknown Source)
at org.gmock.internal.MockInternal.invokeMockMethod(MockInternal.groovy:80)
at org.gmock.internal.metaclass.MockProxyMetaClass$2.call(MockProxyMetaClass.java:53)
at org.gmock.internal.InternalMockController.doWork(InternalMockController.groovy:218)
at org.gmock.internal.InternalMockController.this$2$doWork(InternalMockController.groovy)
at org.gmock.internal.InternalMockController$this$2$doWork.callCurrent(Unknown Source)
at org.gmock.internal.InternalMockController$this$2$doWork.callCurrent(Unknown Source)
at org.gmock.internal.InternalMockController.doInternal(InternalMockController.groovy:207)
at org.gmock.internal.InternalMockController$doInternal.callCurrent(Unknown Source)
at org.gmock.internal.InternalMockController$doInternal.callCurrent(Unknown Source)
at org.gmock.internal.InternalMockController.doInternal(InternalMockController.groovy:200)
at org.gmock.internal.metaclass.MockProxyMetaClass.invokeMethod(MockProxyMetaClass.java:47)
at org.gmock.internal.metaclass.MockProxyMetaClass.invokeMethod(MockProxyMetaClass.java:43)
at es.cuestamenos.dominio.HistoricoTests$_testTodosActivos_closure2.doCall(HistoricoTests.groovy:83)
at es.cuestamenos.dominio.HistoricoTests$_testTodosActivos_closure2.doCall(HistoricoTests.groovy)
at org.gmock.internal.InternalMockController.play(InternalMockController.groovy:116)
at org.gmock.internal.InternalMockController$play.call(Unknown Source)
at org.gmock.GMockController.play(GMockController.groovy:29)
at org.gmock.GMockController$play.call(Unknown Source)
at es.cuestamenos.dominio.HistoricoTests.play(HistoricoTests.groovy)
at es.cuestamenos.dominio.HistoricoTests.testTodosActivos(HistoricoTests.groovy:82)

Anything else I could try?

Johnny Jian said...

I think you should use "list{}" instead of "list()"

Unknown said...

That did the trick. I forgot that I defined the mock as list(instanceOf(Closure)) ;-)

Thanks!

Unknown said...

Actually, I just realized that this trick is great to simulate the named query, but not to test it, as it will always pass the test, right?

What are your thoughts on this?

Rob said...

Yes, this technique is *not* suitable for testing the query. It's for testing the logic in code that uses the query without having to connect to a real database or set up real test data.

All you're doing in that code is testing the mock you just set up.

Unknown said...

Rob, and any idea on how I could test the named query itself meanwhile support for mockDomain is added to Grails?

Ursula said...
This comment has been removed by the author.
Ursula said...

Thanks for the great example. It was a big help to us starting out with Grails.
If your named query takes parameters, you need to define it a bit differently.

def results=[]
def params
def mockCriteria = mock() {
list().returns( results)
}
mock(Keyword).static.myNamedQuery(params)
.returns(mockCriteria)

Unknown said...

Just a remark: mocking static methods using GMock will make tests fall if you or some Grails plugin need to call other static methods on particular class. The fix is to combine GMock with the standard Groovy metaprogramming:

def results = // whatever you want your criteria query to return
def mockCriteria = mock() {
list(instanceOf(Closure)).returns(results)
}
MyDomain.static.createCriteria = { -> mockCriteria}

play {
controller.myAction()
}

agario games said...

Thanks for your sharing! The information your share is very useful to me and many people are looking for them just like me! thank you! I hope you have many useful articles to share with everyone!
slither io

m.k said...

كل سنه وانتم بخير اليكم بعض المواقع الهامه لكل من لديها جهاز بيه اى عطل
صيانة غسالات هيتاشى
صيانة هيتاشى
مبيعات يونيون اير
تكييف يونيون اير
صيانة اوليمبيك
صيانة زانوسي
صيانة سامسونج
صيانة توشيبا
صيانة غسالات زانوسي
صيانة غسالات سامسونج
صيانة ثلاجات كريازي
صيانة ثلاجات توشيبا
صيانة سخانات اوليمبيك موقع
صيانة ثلاجات زانوسي موقع
صيانة سامسونج موقع
صيانة توشيبا شركة
صيانة غسالات توشيبا موقع
صيانة ثلاجات توشيبا موقع
صيانة كريازي موقع
صيانة غسالات اوليمبيك موقع
الموقع الرسمي
صيانة سيمنس موقع
صيانة سخانات سيمنس
صيانة غسالات كريازي
رقم صيانة سيمنس
اسعار تكييف يونيون اير موقع














meldaresearchusa said...

We would like to bring to your attention that, the most effective way one can submit quality Cheap Assignment Writing Help Services papers is by having an agency offering such services alongside others in Term Paper Help Services and custom term papers.

virginiafawcett said...
This comment has been removed by the author.
virginiafawcett said...
This comment has been removed by the author.
virginiafawcett said...

Essentially, an abstract on research paper is an extrapolation of course works written in a report format specified by the professor. Typically, an abstract consist of one paragraph that summarizes the contents of the article in precise terms.

Fashion said...

Thanks for providing great information and looking beautiful blog, extremely decent required data and the things I never envisioned and I would ask for, wright more blog and blog entry like that for us Top Gun Jacket

Unknown said...

This is the first time that I visit here. I found so many exciting matters in this particular blog, One thing I would like to request you that pls keep posting such type of informative blog. Movie Jackets

Unknown said...

I appreciate this blog your blog is vert help full for me i really enjoyed this stuff dude. Spiderman Jacket

AmericanExpress said...

https://americanexpresscomconfirmcard.live/ add comments

Kroger Feed said...

www.krogerfeedbackz.info read reviews.

Priya Escorts said...

Call Girls Nagpur | College Escort in Nagpur | Escorts Service Nagpur | Best Escort services in Nagpur | Escorts services in Nagpur
Independent Call Girls in Nagpur | Russian Escorts in Nagpur | Collage Call Girls in Nagpur | Celebrity Escorts in Nagpur | Call Girls Nagpur Whatsapp No
Nagpur Escorts Whatsapp Phone Number | Nagpur Escorts Phone Number | High Class Nagpur Escorts | http://www.priyaescorts.in/nagpur-escorts.html

John Lilly said...

This blog is very informative thanks for sharing this with us. I appreciate your work. Check out our 100% leather
Top Gun Jacket and also check Film jacket, Gaming jacket, TV series jacket and many more.

Emma Jackson said...

Your blog is awesome I got all those things what I want to know. Thanks for sharing. Check out our new famous outfit The Gentlemen Coach Tracksuit

Skinoutfits said...


very nice post , i found something more intresting Mike Lowrey Bad Boys For Life jacket