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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Service { | |
private Datastore datastore | |
Service(Datastore datastore) { | |
// ... | |
} | |
def processEntry(Map<String, String> someStuffToStore) { | |
// ... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Datastore { | |
boolean open() {} | |
boolean storeField(key, value) { | |
// I should probably store this | |
false | |
} | |
def close() {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.mock.interceptor.MockFor | |
import org.junit.Test | |
class ServiceTest { | |
@Test | |
void testServiceOpensTheDataStoreOnInitialization() { | |
//given | |
def mockForDataStore = new MockFor(Datastore) | |
mockForDataStore.demand.open { true } | |
def mockDataStore = mockForDataStore.proxyInstance() | |
//when | |
def service = new Service(mockDataStore) | |
//then | |
mockForDataStore.verify mockDataStore | |
} | |
} |
- 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.
junit.framework.AssertionFailedError: verify[0]: 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
void testServiceStoresTheValues() { | |
//given | |
def mockForDataStore = new MockFor(Datastore) | |
mockForDataStore.demand.open { true } | |
mockForDataStore.demand.storeField { k, v -> true } | |
def mockDataStore = mockForDataStore.proxyInstance() | |
def service = new Service(mockDataStore) | |
//when | |
service.processEntry([someKey:"someValue"]) | |
//then | |
mockForDataStore.verify mockDataStore | |
} |
junit.framework.AssertionFailedError: verify[1]: expected 1..1 call(s) to 'storeField' but was called 0 time(s)
However we can make it pass without writing any meaningful code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def processEntry(Map<String, String> someStuffToStore) { | |
datastore.storeField("this is a made up key","this is a made up value") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mockForDataStore.demand.storeField { k, v -> assert v == "someValue"; assert k == "someKey" } |
Assertion failed:
assert v == "someValue"
| |
| false
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
void testServiceStoresAllTheValues() { | |
//given | |
def mockForDataStore = new MockFor(Datastore) | |
mockForDataStore.demand.open { true } | |
mockForDataStore.demand.storeField { | |
k, v -> | |
if (k == "someKey") assert(v == "someValue") | |
if (k == "anotherKey") assert(k == "anotherValue") | |
} | |
def mockDataStore = mockForDataStore.proxyInstance() | |
def service = new Service(mockDataStore) | |
//when | |
service.processEntry([someKey:"someValue", | |
anotherKey:"anotherValue"]) | |
//then | |
mockForDataStore.verify mockDataStore | |
} |
This same style can be used to return different values depending on input parameters e.g.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Java Mocktio | |
when(someMock.someMethod("something")).thenReturn("oneThing") | |
when(someMock.someMethod("somethingElse")).thenReturn("anotherThing") | |
//Groovy MockFor | |
someMock.demand.someMethodThatReturns { | |
param1 -> | |
if (param1 == "something") return "oneThing" | |
if (param1 == "somethingElse") return "anotherThing" | |
} |
1 comment:
Hi Chris, I've used something similar to return different results for different invocations. I used an array where each entry is a map of parameters to assert against and a value to return. I then just have some code in the demand closure that pops an entry from the array, separates the params from the return value, does the param assertions and returns the return value.
Post a Comment