Created
May 29, 2017 14:49
-
-
Save sofiaguyang/05f6153e5c87cb6df5de2bd643515b69 to your computer and use it in GitHub Desktop.
TestDoubleInjector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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