XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Mocking only Abstract Methods using Mockito (Partial Mocking)

I remember back in the days, before any mocking frameworks existed in Java, we used to create an anonymous-inner class of an abstract class to fake-out the abstract method’s behaviour and use the real logic of the concrete method.

This worked fine, except in cases where we had a lot of abstract methods and overriding each of those methods to do-nothing or return dummy value seemed like a complete waste of time.

With the mocking frameworks like Mockito, we have a better way to deal with such situations, esp. in legacy code. But there is a catch. Let me explain it to you via a code example.

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello";
	}
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethods() {
		AbstractClazz clazz = mock(AbstractClazz.class);
		assertEquals("Hello", clazz.sayHello());
	}
}

This test fails, with the following error:

java.lang.AssertionError: expected:<Hello> but was:<null>
	at org.junit.Assert.fail(Assert.java:91)
	at org.junit.Assert.failNotEquals(Assert.java:645)
	at org.junit.Assert.assertEquals(Assert.java:126)
	at org.junit.Assert.assertEquals(Assert.java:145)
	at com.agilefaqs.mocking.AbstractClazzTest.
	shouldNotFakeRealMethods(AbstractClazzTest.java:20)

To make this work, We need to pass the following Answer parameter while creating the Mock:

AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);

Now let’s say our requirements have evolved. Our sayHello() method should also add the person’s name and greet. Different implementations will figure out different ways to fetch the person’s name.

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello " + fetchName() + "!";
	}
 
	protected abstract String fetchName();
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

The moment we run this test, we get the following error:

java.lang.AbstractMethodError: 
	com.agilefaqs.mocking.AbstractClazz.fetchName()Ljava/lang/String;
	at com.agilefaqs.mocking.AbstractClazzTest.
	shouldCallRealMethodsAndFakeAbstractMethod(AbstractClazzTest.java:22)

Basically, we need our mocking framework to give us a mock which allows partial mocking. Which means, for some methods we want the real methods to be invoked and for some, we want to use the fake implementation.

One way to implement this is by creating a default mock and then explicitly setting expectation on real methods.

@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
	AbstractClazz clazz = mock(AbstractClazz.class);
	when(clazz.sayHello()).thenCallRealMethod();
	when(clazz.fetchName()).thenReturn("Naresh");
	assertEquals("Hello Naresh!", clazz.sayHello());
}

But the main problem with this approach is that we need to ensure we set explicit expectations on all public and protected methods which might be internally called by the main method (sayHello in this case.) To make matters worse, private methods can’t be mocked and hence we can’t set expectations on them. But let’s say at a later point, if someone makes a private method protected/public, the test will fail, as it will now get mocked. Overall this strategy can make your tests extremely fragile.

For example the following works:

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello " + fetchName() + closingSymbol();
	}
 
	private String closingSymbol() {
		return "!";
	}
 
	protected abstract String fetchName();
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class);
		when(clazz.sayHello()).thenCallRealMethod();
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

However if we change closingSymbol() method to protected/public, the test will fail with the following error:

org.junit.ComparisonFailure: expected:<Hello Naresh[!]> but was:<Hello Naresh[null]>

A better approach in Mockito is to pass a Custom Answer parameter while creating the mock. Following is the Answer implementation which can do partial mocking:

public class AbstractMethodMocker implements Answer<Object> {
	@Override
	public Object answer(InvocationOnMock invocation) throws Throwable {
		Answer<Object> answer;
		if (isAbstract(invocation.getMethod().getModifiers()))
			answer = RETURNS_DEFAULTS;
		else
			answer = CALLS_REAL_METHODS;
		return answer.answer(invocation);
	}
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class, new AbstractMethodMocker());
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

If you are forced to use this technique in brand new code you are building, may I suggest the Delete button…


    Licensed under
Creative Commons License