A blog about software development and other software related matters

Blog Archive

Friday, July 4, 2008

Mocking & Spring tests

Spring supports unit testing quite well, still it seems that its a bit tricky to inject mocks during tests, the documentation does states that using IOC framework can make it easier to do so but doesn't show you how.
Lets take for example the following common scenario, we've got a service that we wish to test which requires a DAO as a part of it functionality:


public interface ListingService {

Person getPerson(String id);

}
public class ListingServiceImpl implements ListingService{

@Autowired
private PersonDAO personDAO;

@Transactional
@Override
public Person getPerson(final String id) {
return personDAO.getPersonById(id);
}

}

Now since we don't want to test the DAO (not integration testing) we are going to need to mock it (EasyMock), not only that but we rather use Spring testing framework that can save us a lot of hard wiring since we are already using auto-wiring in our code.
Usually mocking frameworks provides us with a static factory method that takes an interface and returns a proxy object (on which we may define behavior), this conflicts with the auto-wiring functionality since that wiring is performed according to return type of the factory method (Object) and not according to returned object itself (Spring will fail to inject the mocked DAO proxy into the service as discussed here).
This issue can be resolved by using Spring FactoryBean, this interface includes the getObjectType method that is used by Spring as the return type of this factory (exactly what we need!), all that we need to do is to implement this interface and to delegate the object creation to EasyMock like follows:

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 EasyMock.createMock(type);
}

@Override
public Class getObjectType() {
return type;
}

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

Now our testing context looks like:

<beans name spaces...>
<!-- Mocks -->
<bean id="factory" class="com.jdftm.springmocks.services.mocks.MocksFactory" name="PersonDAO">
<property name="type" value="com.jdftm.springmocks.dao.PersonDAO"/>
</bean>
<!-- The actual service-->
<bean id="listingService" class="com.jdftm.springmocks.services.ListingServiceImpl" autowire="autodetect"/>
</beans>

And our test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/mocksContext.xml"})
public class ListingServicesTest {

@Autowired
private ListingService service;
@Autowired
private PersonDAO personDAO;

@Before
public void setup() {
expect(personDAO.getPersonById("123")).andReturn(new Person("joe", "123"));
replay(personDAO);
}

@Test
public void simplestTest() {
Person person = service.getPerson("123");
assertEquals(person.getId(), "123");
}
}

This approach is useful but not very maintainable since we need to specify all the auto-wired properties (mostly mocks) of each service that we wish to test, it would be much nicer if all the properties are automatically injected for us, this may be achieved by resorting to programmatic bean definition:

public class AutoBeanDeclarer implements BeanFactoryPostProcessor {

private Class mocksHolder;

public void setMocksHolder(final Class mocksHolder) {
this.mocksHolder = mocksHolder;
}

@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory context) throws BeansException {
BeanDefinitionRegistry registry = ((BeanDefinitionRegistry) context);
for (final Field field : findAllAutoWired()) {
registerOn(registry, field.getType());
}
}

private List<Field> findAllAutoWired() {
return (List<Field>) CollectionUtils.select(Arrays.asList(mocksHolder.getDeclaredFields()), new Predicate() {
@Override
public boolean evaluate(Object field) {
return ((Field) field).isAnnotationPresent(Autowired.class);
}
});
}

private void registerOn(final BeanDefinitionRegistry registry,final Class type){
MutablePropertyValues values = new MutablePropertyValues();
values.addPropertyValue(new PropertyValue("type", type));
RootBeanDefinition definition = new RootBeanDefinition(MocksFactory.class, values);
registry.registerBeanDefinition("factory"+type.getSimpleName(), definition);
}
}

This class implements the BeanFactoryPostProcessor interface which enables the registration of beans as Spring loads up a context, the code scans a provided class for fields that carry the @Autowired annotation and registers for each one a matching mock.
For any number of auto-wired fields the context will contain only a single configure line:
 <beans name spaces.../>
<!--Programtic configuration-- >
<bean id="autoConfigurer" class="com.jdftm.springmocks.services.mocks.AutoBeanDeclarer" p:mocksHolder="com.jdftm.springmocks.services.ListingServiceImpl"/>
<!-- The actual service-->
<bean id="listingService" class="com.jdftm.springmocks.services.ListingServiceImpl" autowire="autodetect"/ >
</beans>

Fruitful mocking to us all.

4 comments:

Mick Knutson said...

Can you pleaqse tell me the imports you used for the AutoBeanDeclarer.java class? I can't seem to get this working.

ronen said...

Sure here they are:
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
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.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;

I guess that i should post the code more import dirty next time ;)
Let me know if it dosnt work

Anonymous said...

These mocking tips were really helpful. Apart from this, could you please let me know that how do you put java code and xml/html in blogger post.

Thanks

ronen said...

Well im using SyntaxHighlighter, you could embed it within the Blogger template.