Coming at Groovy from a Java background it wasn't long before I wanted to see what mocking was like in Groovy. I'm fond of Mockito's flavour of mocking where I setup my mocks to behave appropriately, poke the class under test, then verify the correct invocations have been made with the class under test's dependencies. Saying that I have nothing against the setup expectations and then fail at the end if they haven't been met.
So Groovy MockFor puzzled me for a while. Lets look at an example of testing a very simple Service class that is required to process entries in a map and store each of them:
And the Datastore looks like this:
Now lets say we want to test that when the Service is initialised that we "open" the Datastore. This can be achieved with MockFor quite easily:
Lets take see what is going on here.
- On line 9 we create a new MockFor object to mock the DataStore.
- On line 10 we demand that the open method is called and we return true.
- On line 11 we get a proxy instance that we can pass into the Service class.
- On line 14 we create an instance of the Service class passing in our mocked Datastore.
- Finally on line 17 we verify that all our demands have been met.
It is worth pointing out that the MockFor isn't strongly typed, we could have mocked any method even if it doesn't exist. If we run this test we'll get the following failure:
junit.framework.AssertionFailedError: verify: expected 1..1 call(s) to 'open' but was called 0 time(s)
Brilliant. Now lets move onto a move complicated example. Lets say we want to test the processEntry method takes each of the entries and stores them in the Datastore. This is when it became apparent to me that what is happening on line 10 is a closure that is executed when the mocked method is called. It just happened to return true as that was the last statement in the closure and Groovy doesn't always require the return statement. My first attempt to test the above scenario led me to:
Okay so the test fails now:junit.framework.AssertionFailedError: verify: expected 1..1 call(s) to 'storeField' but was called 0 time(s)
However we can make it pass without writing any meaningful code:
But changing line 6 to the following:
Means we'll get the following failure:Assertion failed:
assert v == "someValue"
this is a made up value
And then we can actually write some sensible code to make it pass as now we're actually asserting that the correct values have been passed in.
Now finally onto the problem from the title. How do we mock multiple invocations to the same method with different arguments? We may want to do this when we verify all the values in the map passed to processEntry are passed to the Datastore rather than just the first one. This is where if you are coming from a Java/Mockito background you need to think differently. The solution I used in a similar situation looked like this:
Here we've written more complicated code in the closure that will be executed when the storeField method is called. Depending on what the key is that has been passed in a different assertion is executed. Of course you need to add some more code to the closure if we wanted to verify that no other values have been passed in.
This same style can be used to return different values depending on input parameters e.g.
I've also included the equivalent Java Mockito code for comparison.