Inlining Mocks in Mockito

Mockito normally throws an exception when you try to inline a mock as follows:
Zoo zoo = mock(Zoo.class);
when(zoo.getAnimal()).thenReturn(mock(Animal.class));

One work-around is to do the following:
Zoo zoo = mock(Zoo.class);
Animal animal = mock(Animal.class);
when(zoo.getAnimal()).thenReturn(animal);

However, the test code is annoyingly verbose if we need more complex mocking:
Zoo zoo = mock(Zoo.class);

Animal tiger = mock(Animal.class);
Animal monkey = mock(Animal.class);
Animal zebra = mock(Animal.class);

when(tiger.getName()).thenReturn("Tiger");
when(monkey.getName()).thenReturn("Monkey");
when(zebra.getName()).thenReturn("Zebra");

when(zoo.getTiger()).thenReturn(tiger);
when(zoo.getMonkey()).thenReturn(monkey);
when(zoo.getZebra()).thenReturn(zebra);

If only the unfinished stub verification didn't throw errors during inline mocking, we could refactor the test code into something like this:
MockInliner.install();
Zoo zoo = mock(Zoo.class);
when(zoo.getTiger()).thenReturn(mockAnimal("Tiger"));
when(zoo.getMonkey()).thenReturn(mockAnimal("Monkey"));
when(zoo.getZebra()).thenReturn(mockAnimal("Zebra"));

So I wrote a class to get around it. Copy and paste the following class into your project:
package ripper;

import java.lang.reflect.Field;

import org.mockito.Mockito;
import org.mockito.exceptions.misusing.UnfinishedStubbingException;
import org.mockito.internal.MockitoCore;
import org.mockito.internal.progress.*;

@SuppressWarnings("unchecked")
public class MockInliner {
 public MockInliner() {
  install();
 }

 public static void install() {
  trySetMockingProgressImplementation(new InlineMockingProcess());
 }

 public static void uninstall() {
  trySetMockingProgressImplementation(new MockingProgressImpl());
 }

 private static void trySetMockingProgressImplementation(MockingProgress progress) {
  try {
   setMockingProcessImplementation(progress);
  } catch (Exception e) {
   throw new RuntimeException("Unable to set mocking progress, Mockito implementation must have changed", e);
  }
 }

 private static void setMockingProcessImplementation(MockingProgress progress) throws Exception {
  Object core = getHiddenField(Mockito.class, null, "MOCKITO_CORE");
  Object mocking = getHiddenField(MockitoCore.class, core, "mockingProgress");
  Object implementation = getHiddenField(ThreadSafeMockingProgress.class, mocking, "mockingProgress");
  ((ThreadLocal<MockingProgress>) implementation).set(progress);
 }

 private static Object getHiddenField(Class<?> clazz, Object obj, String name) throws Exception {
  for (Field field : clazz.getDeclaredFields())
   if (field.getName().equals(name)) {
    field.setAccessible(true);
    return field.get(obj);
   }
  throw new NoSuchFieldException("Field " + name + " not found");
 }

 private static class InlineMockingProcess extends MockingProgressImpl {
  @Override
  public void validateState() {
   try {
    super.validateState();
   } catch (UnfinishedStubbingException e) {
    // Do not fail
   }
  }
 }
}

Full disclosure, the code is just a hack to suppress the exception, tied to the implementation of Mockito through reflection. Once installed, Mockito won't check for unfinished stubs. Anyway here is the test code for those interested:
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.mockito.exceptions.misusing.UnfinishedStubbingException;

public class MockInlinerTest {

 @Test(expected = UnfinishedStubbingException.class)
 public void cannotNormallyInlineMocks() throws Exception {
  Zoo zoo = mock(Zoo.class);
  when(zoo.getTiger()).thenReturn(mock(Animal.class));
 }

 @Test
 public void canInlineMocksWithInlinerInstalled() throws Exception {
  MockInliner.install();
  Zoo zoo = mock(Zoo.class);

  when(zoo.getTiger()).thenReturn(mockAnimal("Tiger"));
  when(zoo.getMonkey()).thenReturn(mockAnimal("Monkey"));
  when(zoo.getZebra()).thenReturn(mockAnimal("Zebra"));

  assertThat(zoo.getMonkey().getName(), equalTo("Monkey"));
 }

 private Animal mockAnimal(String name) {
  Animal animal = mock(Animal.class);
  when(animal.getName()).thenReturn(name);
  return animal;
 }

 private interface Zoo {
  Animal getZebra();

  Animal getMonkey();

  Animal getTiger();
 }

 private interface Animal {
  String getName();
 }
}

1 comment:

  1. wow that is some serious work for a work around. Thanks :)

    ReplyDelete