Mocking Arbitrary Depth Level Beans in Spring Tests

Recently I had to find a solution for mocking arbitrary-depth-level beans in Spring tests; when saying "arbitrary-depth-level beans" I mean a situation where, for example, I have the following Autowiring Chain:

 

Test Class -> ServiceA -> ConnectorFactoryBean(Singleton) -> ConnectorBean (Prototype) -> ConnnectionFactory(Singleton) -> Connection (Prototype).

 

During test execution, I'd like to mock the connection factory used by the ConnectorBean, thus opening a fixed-result connection instead of opening a real external data source connection which is not needed by my test, as I'm testing higher level functionality.

Had the object dependency graph been simpler, I could have just injected the mock into the service in the test itself, but that is quite possible, especially as prototype beans are tricky to follow.

 

At first, I tried using a static inner class inside my test, that was annotated with @Configuration and having @Bean+@Primary methods that created my mocks programmatically with the help of EasyMock (see here if you need an explanation of these annotations and Spring's Java config). Everything worked just fine at first, but than I added another test that had to use a different configuration, but was injected with the same mick beans I created in the previous test! In other words, I now had to localize the test configuration and prevent my mock beans from leaking to other tests.

 

I than came across an interesting implementation of Spring's ContextLoader, org.apache.camel.spring.javaconfig.test.JavaConfigContextLoader, which takes the "locations" attribute usually used to specify xml resource file locations (for example, as in@ContextConfiguration(locations = { "classpath:db-context.xml")}) ) and applies a classloading semantics for tha attribute - in other words, treats the resources as fully qualified class names, and then tries to load them as Configuration files. Nice, now all I had to write in my test is:

 

 

@ContextConfiguration(loader = JavaConfigContextLoader.class, locations={"com.blah.MyTest"}) 
@ImportResource({"/db-context.xml", "/startup-context.xml"}) 
@DirtiesContext 
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
	DependencyInjectionTestExecutionListener.class,
	TransactionalTestExecutionListener.class,
	DirtiesContextTestExecutionListener.class})
public class MyTest {

    @Autowired
    private MyService testedService;

    /*************************
     *  Test Mocks
     *************************/
    @Bean
    @Primary
    MyConnectionFactory myConnectionFactory() {
        // Simplified code, for the sake of brevity
        return EasyMock.createNiceMock(MyConnectionFactory.class);
    }

    @Test
    public void testMyService() {
        // test code...
    }

}

 

Note, that I needed the @DirtiesContext annotation to make sure that my mocked beans do not stay in the test context and, again, leak into other tests.

 

Hmm, nice.

 

3 more improvements were in place:

 

One: getting rid of the cumbersome JavaConfigContextLoader configuration by eliminating the need to explicitely specify the name of the @Configuration class - assuming it is always the name of the class where the ContextConfiguration annotation is placed - which is exactly the case for test classes. I wrote the following simple implementation of ContextLoader, extending Camel's excellent  implementation:

 

import org.apache.camel.spring.javaconfig.test.JavaConfigContextLoader;
import org.springframework.context.ApplicationContext;

public class MockContextLoader extends JavaConfigContextLoader {

    ThreadLocal<String> testClass = new ThreadLocal<String>();
   
    @Override
    public String[] processLocations(Class<?> clazz, String... locations) {
        testClass.set(clazz.getName());
        return super.processLocations(clazz, locations);
    }

    @Override
    public ApplicationContext loadContext(String... locations) {
        try {
            return super.loadContext(testClass.get());
        }
        finally {
            testClass.remove();
        }
    }
}

 

 

I than added another annotation for eliminating the need to specify @Bean and @Primary on every mock-generating method. I only recently discovered annotation contageousness which is super-sweet. All I had to do is annotate my new annotation class with the annotations I needed shortened, and voilla! Meet my new super-annotation:

 

@Primary
@Bean
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SpringMock {}

 

 

Last, I moved the repeating annotations(@RunWith, @TestExecutionListeners and @DirtiesContext)  to a super-class, MockGeneratingSpringTest.

 

Finally, my test class now looks like this:

 

 

@ContextConfiguration(loader = MockContextLoader.class)
@ImportResource({"/db-context.xml", "/startup-context.xml"})
public class MyTest extends MockGeneratingSpringTest {

    @Autowired
    private MyService testedService;

    /*************************
     *  Test Mocks
     *************************/
    @SpringMock
    MyConnectionFactory myConnectionFactory() {
        // Simplified code, for the sake of brevity
        return EasyMock.createNiceMock(MyConnectionFactory.class);
    }

    @Test
    public void testMyService() {
        // test code...
    }

}

 

 

trimmed-down to bare necessities, just the way I like it...

 

Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com