Skip to content

Instantly share code, notes, and snippets.

@raphaeljolivet
Last active December 19, 2015 06:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raphaeljolivet/5909717 to your computer and use it in GitHub Desktop.
Save raphaeljolivet/5909717 to your computer and use it in GitHub Desktop.
Junit Test Runner that automagically stubs Spring's @Autowired fields (setters) with Mockito mockups.
/**
* Smart JunitRunner that autowires fields together for @Autowire (Spring) fields,
* or create empty mock otherwize.
* The properties marked with @AutoMock will be autwired with other properties
* marked with @AutoMock or new empty mock.
*/
public class AutoMockTestRunner extends BlockJUnit4ClassRunner {
@Retention(RUNTIME)
@Target(FIELD)
static public @interface AutoMock {
};
private Logger logger = LoggerFactory.getLogger(AutoMockTestRunner.class);
/** Map of avaiable mocks + objects to mock */
private Map<Class, Object> instances = newHashMap();
public AutoMockTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected Object createTest() throws Exception {
Object testClassInstance = super.createTest();
Map<Field, Object> fields = newHashMap();
// Get all fields annotated with @AutoMock
for (Field field : testClassInstance.getClass().getDeclaredFields()) {
if (field.getAnnotation(AutoMock.class) == null) {
continue;
}
field.setAccessible(true);
Object instance = field.get(testClassInstance);
if (instance == null) {
throw new RuntimeException(format("The field #%s is null : Cannot autowire", field.getName()));
}
fields.put(field, instance);
instances.put(instance.getClass(), instance);
}
for (Entry<Field, Object> field : fields.entrySet()) {
logger.info("Autowiring #{}", field.getKey().getName());
autoMockInstance(field.getValue());
}
return testClassInstance;
}
/** Look for @autowire annotations and either : Wire with available instance, or create new empty mock */
private void autoMockInstance(Object autoMockable) {
// Look for @Autowire setters
for (Method setMethod : autoMockable.getClass().getMethods()) {
if (!setMethod.getName().startsWith("set")) {
continue;
}
if (setMethod.getAnnotation(Autowired.class) == null) {
continue;
}
Class setClass = setMethod.getParameterTypes()[0];
Object instance = searchInstance(setClass);
logger.info("Autoriwing field {}", setMethod.getName());
// Let's mock !
if (instance == null) {
logger.info("Class {} not found => mocking", setClass.getSimpleName());
instance = mock(setClass);
instances.put(setClass, instance);
}
// Wire it
try {
setMethod.invoke(autoMockable, instance);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/** Search instance with compatible type */
private Object searchInstance(Class clazz) {
for (Object instance : instances.values()) {
if (clazz.isInstance(instance)) {
return instance;
}
}
return null;
}
}
@raphaeljolivet
Copy link
Author

This snippnet is inspired from @InjectMocks, but more specifically designed to work with Spring's @Autowired.

Usage :

The fields of the test class annotated with @automock will have their @Autowired fields injected with :

  • Other @automock fields of the test class if the type fits.
  • Empty mockito objects

If you want to define custom behavior for mocked up objects, you have to declare them manually in order to reference them in the @before method.

@RunWith(AutoMockTestRunner.class) {

  @AutoMock
  private SomeServiceToTest service = new SomeServiceToTestImpl();

  @AutoMock
  private SomeConfService conf = mock(ConfService.class);

  @Before
  public setUp()  {
     when(conf.getSetttings()).thenReturn(3L);
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment