Mocking beans in Spring
In this article we’ll see how to mock beans (POJOs managed by Spring). Suppose we want to replace some of our beans with mocks while the tests run.
Take an interface FooInterface, its implementation FooBean,
and a class BeanWorkingWithFoo that uses this interface to do its work.
public interface FooInterface {
public String getSomething();
}
public class FooBean implements FooInterface {
private String something;
public FooBean(String something) {
super();
this.something = something;
}
public String getSomething() {
return something;
}
}
public class BeanWorkingWithFoo {
private FooInterface fooInterface;
public FooInterface getFooInterface() {
return fooInterface;
}
public void setFooInterface(FooInterface fooInterface) {
this.fooInterface = fooInterface;
}
}
The Spring configuration then looks as follows.
We have the following code to test BeanWorkingWithFoo.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:META-INF/applicationContext.xml",
})
public class PrimaryBeanTest {
@Autowired
private BeanWorkingWithFoo beanWorkingWithFoo;
@Test
public void testSomething() {
}
}Now we want to make sure that the BeanWorkingWithFoo instance under test uses a mock FooInterface bean. What are our options? The simplest approach is probably to create a mock in the test’s setup method and set it on the BeanWorkingWithFoo instance. Unfortunately, the code doesn’t always lend itself to this. We might lack the necessary setter, or the object we want to mock might be nested deeper in the object structure. That’s solvable, though the solution won’t be elegant.
An elegant solution would be to define a test context, declare the mock as a bean there, and let Spring override the original bean with the mock. We can do that by giving the mock bean the same ID as the original and making sure the context with the mock loads second. BeanFactory will then override the original bean with that ID.
This solution has two drawbacks. We must always ensure that the bean being overridden has an ID and that the context defining the mocks loads last. The first is a drawback because requirements specific to the tests dictate implementation details. We don’t always want to reference our beans by ID, and may prefer autowiring by type. The second is that when we load contexts using a classpath wildcard, it is hard to guarantee their order. It depends on the order of the JARs on the classpath.
I mentioned autowiring by type mainly in connection with annotation-driven dependency injection, which uses type by default. In our case, we could declare BeanWorkingWithFoo with annotations instead of in XML (setting aside the fact that Qualifier could enforce references by ID).
@Component
public class BeanWorkingWithFoo {
@Autowired
private FooInterface fooInterface;
public FooInterface getFooInterface() {
return fooInterface;
}
public void setFooInterface(FooInterface fooInterface) {
this.fooInterface = fooInterface;
}
}Quiz question: what happens if we have several beans (the mock and the real implementation) of the same interface, FooInterface, and how can we prevent it?
At startup, the IoC container tells us it can’t decide which bean to use and exits with an error. For this case (multiple candidates for autowiring by type), we can use a technique a colleague pointed out to me (thanks, Sváťa :-) that I might otherwise have missed in the Spring documentation. See the section Autowiring collaborators. We can mark our mock bean as primary. If one of the beans is marked primary, it is automatically chosen as the best candidate for autowiring.
That covers autowiring by type. We still have to deal with cases where it is hard to control the order in which contexts load. Here we can use one of the IoC container’s extension points. We’ll implement BeanFactoryPostProcessor so we can override beans programmatically. To override named beans we’ll use aliases. The ID of the original bean becomes an alias for the mock bean. All references to the original implementation by name will then resolve to the mock.
public class MockPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
//Ziskame jmena vsech bean s rozhranim FooInterface
String beanNames[] = beanFactory.getBeanNamesForType(FooInterface.class);
//Jedna z techto bean je nas mock a tu mi musime najit
//resp. jeji jmeno, na ktere ukaze alias. Pro lepsi rozpoznavani
//jestli je bean mock a nebo neni jsem si udelal specialni
//anotaci Mock. Diky tomu bezpecne poznam beanu mocku.
String mockBeanId = null;
for(String beanName : beanNames) {
Object bean = beanFactory.getBean(beanName);
if(bean.getClass().getAnnotation(Mock.class) != null) {
mockBeanId = beanName;
break;
}
}
//Nyni uz staci nastavit idcko naseho mocku jako alias
//pro vsechny puvodni idcka.
if(mockBeanId != null) {
for(String beanName : beanNames) {
if(!beanName.equals(mockBeanId)) {//osetren alias sam na sebe
beanFactory.registerAlias(mockBeanId, beanName);
}
}
}
}
}Add this post-processor to the context alongside the mock definition.
Of course, the post-processor can be implemented generically so it can set up aliases for all declared mocks.
Medium version · Archived original page
Related articles
Related CZ Podcast episodes
- #261 · Leadership
On leading people and teams. · Czech audio
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio