Mocking Java classes in Groovy vs Mockito

While experimenting with using an ODF parser in Groovy, I was attempting to write unit tests in Groovy and mocking objects from the ODF parser library was required in my tests. Prior to this, most of experience in writing unit test was in Java and mocking using Mockito. Since I had been working in Groovy, I decided to see what support Groovy provides for mocking and how it compares to Mockito (the documentation for mocking in Groovy is available on the Groovy Mocks page).

The mocking examples below will provide mocks for this interface:

public interface Filter<T> {
	public boolean evaluate(T item);
	
	public boolean isEnabled();
}

Mocking in Groovy by map coercion and Expando

Groovy provides the ability to coerce a map. The following provides a filter where both method always return true:

def filter = [ evaluate : {true}, 
               isEnabled: {true}] as Filter

Groovy’s Expando allows methods to seemingly be added and provides another way of mocking:

def filter = new Expando() 
filter.evaluate = {true}
filter.isEnabled = {true}

Compare this to using the Mockito mocking framework:

Filter<? super Object> filter = Mockito.mock(Filter.class);
Mockito.when(filter.evaluate(Mockito.any(Object.class))).thenReturn(true);
Mockito.when(filter.isEnabled()).thenReturn(true);

You can clearly see that Groovy allows us to write the mocks in a much more concise manner!

Mocking in Groovy using MockFor and StubFor

Groovy also provide MockFor and StubFor for mocking objects. In the following code, the filter is mocked to pass only the value of 2:

// Using MockFor:
MockFor filterMock = new MockFor(Filter)
filterMock.ignore.isEnabled {true}
filterMock.ignore.evaluate {it == 2}

// Mocked instance from MockFor		
def mockForFilterInstance = filterMock.proxyDelegateInstance()

// Using StubFor:
StubFor filterStub = new StubFor(Filter)
filterStub.ignore.evaluate {it == 2}
filterStub.ignore.isEnabled {true}

// Mocked instance from StubFor
def stubForFilterInstance = filterStub.proxyDelegateInstance()

Verifying Method Invocations

MockFor and StubFor can also verify that the methods are invoked. Methods that need to be verified that it was invoked added to the “demand” and others can be added to “ignore”. For example, the following will verify that isEnabled was invoked mocked object (filter):

MockFor filterMock = new MockFor(Filter)

// Methods invocations that need to verified that it was invoked should
// be added to demand. For example, this will make "verify" check that 
// "isEnabled" was invoked.
filterMock.demand.isEnabled {true}

// Use ignore if the invocation does NOT need to be verified. When
// "verify" is called later on the mock, invocation of this method
// will NOT be checked.
filterMock.ignore.evaluate {it == 2}

def filter = filterMock.proxyDelegateInstance()

// This is the expected invocation.
filter.isEnabled()

filterMock.verify(filter)

Mocks created using MockFor will expect the methods to be invoked in the order that they were mocked in.
On the other hand, StubFor the order does not matter. A range can also be provided to indicate the number of times that the method is expected to be invoked (thanks to Steinar for pointing this out to me):

// Expects isEnabled to be called exactly 2 times.
mockContext.demand.isEnabled(2) {true}

// Expects isEnabled to never be called.
mockContext.demand.isEnabled(0) {true}

// Expects to be called at least 2 times.
mockContext.demand.isEnabled(2..Integer.MAX_VALUE) {true}

// Expects to be called at most 2 times.
mockContext.demand.isEnabled(0..2) {true }

// Expects isEnabled to be called between 2 and 4 times.
mockContext.demand.isEnabled(2..4) {true}

In comparison, using Mockito:

Filter<? super Object> filter = mock(Filter.class);

// Expects isEnabled to be called exactly 2 times.
Mockito.verify(filter, Mockito.times(2)).isEnabled();

// Expects isEnabled  to never be called.
Mockito.verify(filter, Mockito.never()).isEnabled();

// Expects isEnabled  to be called at least 2 times.
Mockito.verify(filter, Mockito.atLeast(2)).isEnabled();

// Expects isEnabled  to be called at most 2 times.
Mockito.verify(filter, Mockito.atMost(2)).isEnabled();

// Expects isEnabled to be called between 2 and 4 times.     
Mockito.verify(filter, Mockito.atLeast(2)).isEnabled();
Mockito.verify(filter, Mockito.atMost(4)).isEnabled();

One area where I feel Mockito is better is verifying that the methods were invoked with the correct arguments, especially in a case where the method may be invoked multiple times with different arguments:

// Expect evaluate to be called with argument of 2 twice and
// argument of 3, 3 times (in any order).
Mockito.verify(filter, Mockito.times(2)).evaluate(2);
Mockito.verify(filter, Mockito.times(3)).evaluate(3);

One way of doing this in Groovy is to store the count in a map and then asserting on the count:

// This makes a map that provides a default value of 0
// for keys that are not set.
def argCount = [:].withDefault{0}

def filter = [evaluate: {
    argCount[it] += 1
    return true
}] as Filter

assert argCount == [2:2, 3:3]

In this case, the Mockito method has allowed us to express that the test is only interested in the method being called the right number of times with the right arguments. Fortunately, Groovy seamlessly integrates with Java and allows us to use the best of both worlds!

Advertisements

2 Responses to Mocking Java classes in Groovy vs Mockito

  1. Shaun Abram says:

    Really useful summary of using mocks in Groovy, thanks for posting.
    (Groovy Mocks page link is broken though).

    Thanks,

    Shaun

  2. Pingback: Groovy can easily mock Java interfaces | Mocking

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: