Justin has posted 1 posts at DZone. View Full User Profile

Automatically Inject Mocks into Spring Context

02.03.2011
| 11137 views |
  • submit to reddit
When bringing up a Spring context for testing, sometimes following all the dependencies can be a never ending path of bean definitions. In most cases, mocking them out is just fine, and even beneficial if you need to verify() a method on the Mock. We had started with a MocksFactory to inject a mock, but that required a bean definition for every bean being auto wired up. So we came up with a BeanDefinitionRegistryPostProcessor to fill-in all the auto wired beans which we didn't want to waste time defining. Once the definitions have been read in, this bean is given the opportunity to inject some additional beans before the autowiring happens. If an @Autowired field is found, which doesn't have a bean with name of the field, this AutoBeanDeclarer will create a mock for it. This assumes that you're using name matching for Autowiring and not just type matching. It allowed us to remove hundreds of MockFactory bean definitions, without sacrificing the testability of the code.

 

package testing;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
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;

/**
* Initial idea from: http://javadevelopmentforthemasses.blogspot.com/2008/07/mocking-spring-tests.html
*
* Usage: <bean id="autoConfigurer" class="testing.AutoBeanDeclarer"></bean>
*
* @author jryan
*/
public class AutoBeanDeclarer implements BeanDefinitionRegistryPostProcessor {

private Collection<string> mockedDefinitions;

public AutoBeanDeclarer() {
mockedDefinitions = new ArrayList<string>();
}

private Iterable<field> findAllAutoWired(Class targetBean) {
List<field> declaredFields = Arrays.asList(targetBean.getDeclaredFields());
return Iterables.filter(declaredFields, new Predicate<field>() {
@Override
public boolean apply(Field input) {
return input.isAnnotationPresent(Autowired.class);
}
});
}

private void registerOn(final BeanDefinitionRegistry registry,final String beanName, final Class type){
RootBeanDefinition definition = new RootBeanDefinition(MocksFactory.class);

MutablePropertyValues values = new MutablePropertyValues();
values.addPropertyValue(new PropertyValue("type", type));
definition.setPropertyValues(values);

registry.registerBeanDefinition(beanName, definition);
}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for(String beanName: registry.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
String beanClassName = beanDefinition.getBeanClassName();
try {
Class beanClass = Class.forName(beanClassName);
for (final Field field : findAllAutoWired(beanClass)) {
String fieldName = field.getName();
boolean invalidType = field.getType().isArray() || field.getType().isPrimitive();
if( invalidType ) {
continue;
}
if( !registry.isBeanNameInUse(fieldName) ) {
registerOn(registry, fieldName, field.getType());
mockedDefinitions.add(fieldName);
// Now field will be available for autowiring.
}
}
} catch (ClassNotFoundException ex) {
Logger.getLogger(AutoBeanDeclarer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for(String beanName: mockedDefinitions) {
if( !beanFactory.containsBean(beanName) ) {
Logger.getLogger(AutoBeanDeclarer.class.getName()).log(Level.SEVERE, "Missing definition %s", beanName);
}
}
}
}
</field></field></field></string></string>

 

3
Your rating: None Average: 3 (1 vote)
Published at DZone with permission of its author, Justin Ryan.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Martin Flower replied on Wed, 2011/02/09 - 2:29am

Sorry Ryan, I don't quite see how I use this. Do I just declare it as a bean in the application context and then everything works transparently - or do I need to manipulate it in the junit test ? Thanks Martin

Justin Ryan replied on Mon, 2011/02/28 - 9:55am

Exactly like that. You define this bean once, like in the javadoc of the class. Then any required dependencies which can't be found will be Mocked using a MocksFactory. It's pretty trivial to make a Mockito Mocks Factory, here's an example of one. You can see the MocksFactory referenced above.
import org.mockito.Mockito;
import org.springframework.beans.factory.FactoryBean;

/**
 * http://javadevelopmentforthemasses.blogspot.com/2008/07/mocking-spring-tests.html
 *
 * @author jryan
 */
public class MocksFactory implements FactoryBean {

    private Class type;// the created object type

    public void setType(final Class type) {
        this.type = type;
    }

    @Override
    public Object getObject() throws Exception {
        return Mockito.mock(type);
    }
  
    @Override
    public Class getObjectType() {
        return type;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

Ilya Yeliseykin replied on Thu, 2013/12/12 - 9:38am

Great and elegant solution.
Thank you!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.