Skip to content

Instantly share code, notes, and snippets.

@sofiaguyang
Created May 29, 2017 14:49
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 sofiaguyang/05f6153e5c87cb6df5de2bd643515b69 to your computer and use it in GitHub Desktop.
Save sofiaguyang/05f6153e5c87cb6df5de2bd643515b69 to your computer and use it in GitHub Desktop.
TestDoubleInjector
package com.github.sofiaguyang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import java.util.HashMap;
import java.util.Map;
/**
* This is a test utility to inject test doubles into a spring context.
* <p>
* A test double is a generic term for any case where you replace a production object with a mock, stub, spy, dummy,
* or fake to facilitate testing. <a href="http://www.martinfowler.com/bliki/TestDouble.html">Read more about
* test doubles from Martin Fowler's website</a>
* <p>
* Usage:
* <ol>
* <li> BEFORE context is instantiated and initialized in your test class (usually @BeforeClass in JUnit, or setupSpec() in Spock)
* create a TestDoubleInjector and make use of {@link #inject(String, Class, Object)}
* <li> Include a TestDoubleInjector bean in your context. This bean is the actual instance that will do the post processing
* to inject the test doubles that were defined via usage of {@link #inject(String, Class, Object)}
* </ol>
* Notes:
* <ul>
* <li>Using the TestDoubleInjector will modify the application context. Therefore, annotate the test class with @DirtiesContext
* to have subsequent tests be supplied with fresh contexts.
* <li>The TestDoubleInjector will automatically cleanup test double definitions when the application context shuts down.
* Manually call {@link #cleanup()} if necessary.
* <li>Test doubles defined via {@link #inject(String, Class, Object)} will cause a class cast exception if a bean is referenced
* via method call in Java config. Modify the Java config to reference the bean via @Autowired instead.
* </ul>
*
* @author Sofia Ang
*/
public class TestDoubleInjector implements BeanDefinitionRegistryPostProcessor, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(TestDoubleInjector.class);
private static final int CLAZZ = 0;
private static final int OBJECT = 1;
private static Map<String, Object[]> testDoubleBeans = new HashMap<>();
/**
* Injects a {@link TestDoubleFactory} definition, that will return the testDouble argument,
* to supply an undefined bean definition, or replace an existing bean definition.
*
* @param beanName
* @param testDouble
* @param testDoubleType
*/
public void inject(String beanName, Class testDoubleType, Object testDouble) {
testDoubleBeans.put(beanName, new Object[]{testDoubleType, testDouble});
}
/**
* TestDoubleInjector will automatically clean up when the application context shuts down.
* Feel free to do a manual cleanup however if necessary.
* <p/>
* Only do a cleanup when the context has not yet been initialized or has already been destroyed.
* Otherwise, null pointer exceptions may be thrown.
*/
public void cleanup() {
testDoubleBeans.clear();
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (Map.Entry<String, Object[]> beanEntry : testDoubleBeans.entrySet()) {
String beanName = beanEntry.getKey();
Class clazz = (Class<?>) beanEntry.getValue()[CLAZZ];
Object bean = beanEntry.getValue()[OBJECT];
MutablePropertyValues values = new MutablePropertyValues();
values.addPropertyValue(new PropertyValue("bean", bean));
values.addPropertyValue(new PropertyValue("clazz", clazz));
RootBeanDefinition definition = new RootBeanDefinition(TestDoubleFactory.class);
definition.setPropertyValues(values);
registry.registerBeanDefinition(beanName, definition);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//Do nothing
}
@Override
public void destroy() throws Exception {
LOGGER.info("Application context is shutting down... Clearing out test double definitions...");
cleanup();
}
private static class TestDoubleFactory implements FactoryBean {
Object bean;
Class clazz;
@Override
public Object getObject() throws Exception {
return bean;
}
@Override
public Class<?> getObjectType() {
return clazz;
}
@Override
public boolean isSingleton() {
return true;
}
public void setBean(Object bean) {
this.bean = bean;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment