Easy testing with Guice, EasyMock and a custom JUnit runner

By | 19. September 2012

As somebody who develops test driven I face each time almost the same problem: How I could get easy around the dependency problem in my unit tests?

For example: The MapsService should be tested and it has a dependency to a LocationProvider. But how to handle those kind of dependencies? As I work with Guice I would prefer a way like this:

@RunWith(GuiceAndEasyMockTestRunner.class)
public class MapsServiceTest {
 
	@Inject
	private MapsService serviceUnderTest;
 
	@Mock
	private LocationProvider locationProvider;
 
	@Test
	public void injectMockAsLocationProvider() {
		assertEquals(locationProvider, serviceUnderTest.getLocationProvider());
	}
}

I would like to achieve the following in my unit tests:

  • Inject anything that is managed by Guice direct into my test class.
  • Create mocks for any dependency by applying a @Mock annotation.
  • Inject the mocked dependencies into the test class.

1. Let’s start with the @Mock annotation

/**
 * Annotate each field in a test class with @Mock to get a mock injected 
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mock {
 
	/**
	 * @return True, if a nice mock should be created. False otherwise
	 */
	boolean nice() default true; 
}

Nothing fancy here. The nice() attribute indicates if a strict or a nice mock should be created. Read the EasyMock documentation to find out the difference.

2. The interface to Guice

Each test case should be able to define it’s own dependencies and access already defined dependencies. Therefore the GuiceAware interface returns a list of Guice modules and gets later the injector set.

public interface GuiceAware {
 
	/**
	 * @return A list of guice modules to create the injector from.
	 */
	List<Module> getGuiceModules();
 
	/**
	 * The injector will be set after guice is initialized and available for use.
	 * 
	 * @param injector The Injector
	 */
	void setInjector(Injector injector);
}

3. GuiceAndEasyMockTestRunner – Where things come together

I extend the BlockJUnit4ClassRunner and override the withBefores method which get called before each test execution. The target parameter is a virgin instance of my test case. So the runner analyses the defined and annotated dependencies of the test and create mocks for them. Then a Guice injector is created and all the dependencies of my test instance get injected. At the last the runner resets all the created mocks to clear potential EasyMock recordings during the injection process.

public class GuiceAndEasyMockTestRunner extends BlockJUnit4ClassRunner {
 
	public GuiceAndEasyMockTestRunner(Class<?> klass) throws InitializationError {
		super(klass);
	}
 
	@Override
	protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
		try {
			final List<Object> mocks = createAndSetMocks(target);
			injectMembers(target);
			EasyMock.reset(mocks.toArray());
		} catch (Exception ex) {
			throw new RuntimeException("Could not create mocks and wire dependencies.", ex);
		}
 
		return super.withBefores(method, target, statement);
	}
	...
}

Here the contents of the createAndSetMocks() method. It handles all with @Mock annotated fields and create for each one a new mock.

public class GuiceAndEasyMockTestRunner extends BlockJUnit4ClassRunner {
	...
        private List<Object> createAndSetMocks(Object testInstance) throws Exception {
		final List<Object> mocks = new ArrayList<Object>();
		final Field[] fields = testInstance.getClass().getDeclaredFields();
 
		for (Field field : fields) {
			final Mock mock = field.getAnnotation(Mock.class);
			if (mock == null) {
				continue;
			}
 
			final Class<?> klass = field.getType();
			final Object mockInstance = mock.nice() ? EasyMock.createNiceMock(klass) : EasyMock.createMock(klass);
			field.setAccessible(true);
			field.set(testInstance, mockInstance);
			mocks.add(mockInstance);
		}
 
		return mocks;
	}
	...
}

And here the injectMembers() method that expects the test instance to implement the GuiceAware interface.

public class GuiceAndEasyMockTestRunner extends BlockJUnit4ClassRunner {
	...
	private void injectMembers(Object testInstance) {
		if (testInstance instanceof GuiceAware) {
			GuiceAware guiceAware = (GuiceAware) testInstance;
			Injector injector = Guice.createInjector(guiceAware.getGuiceModules());
			injector.injectMembers(testInstance);
			guiceAware.setInjector(injector);
		}
		else {
			throw new IllegalArgumentException("Test class must implement the GuiceAware interface!");
		}
	}
	...
}

4. Abstract test class to makes things easy.

For now each test class has to implement the GuiceAware interface. To avoid this and makes things comfortable I define an abstract test class:

@RunWith(GuiceAndEasyMockTestRunner.class)
public abstract class AbstractGuiceTestCase extends AbstractModule implements GuiceAware {
	protected Injector injector;
 
	@Override
	public List<Module> getGuiceModules() {
		return Arrays.asList(new TestGuiceModule(), this);
	}
 
	@Override
	public void setInjector(Injector injector) {
		this.injector = injector;
	}
 
	@Override
	protected void configure() {}
}

And there is one more thing: The test can act itself as a Guice module as the AbstractGuiceTestCase extends Guice’s AbstractModule. This makes things really easy, because each test case can define the expected dependencies in the configure() method.

public class MapsServiceTest extends AbstractGuiceTestCase {
 
	@Inject
	private MapsService serviceUnderTest;
 
	@Mock
	private LocationProvider locationProvider;
 
	@Test
	public void injectMockAsLocationProvider() {
		assertEquals(serviceUnderTest.getLocationProvider, locationProvider);
	}
 
	@Override
	protected void configure() {
		bind(LocationProvider.class).toInstance(locationProvider);
	}
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.