Automatic mocking of spring beans with MockedLoader
The problem
We all love unit testing and Spring's JavaConfig. And we certainly dig them beans, however for unit testing we need to mock or instantiate our beans with Test @Configuration classes (or reuse existing @Configuration classes).
In the case of mocking, we might see that adding an extra @Autowired dependency in e.g. a Service class, might break some tests...well .. sometimes a lot of tests.
After fixing those, we might see more broken tests ... due to transitive dependencies introduced from the Service we just added and we need to fix them again.
The solution
MockedLoader will automatically mock any bean that is @Autowired, for you - without needing any code in your @Configuration class. The concept is close to the @Mock annotation - with the difference that it gives you more flexibility on how you can interact with the mocked bean.
Example scenario's without MockedLoader
Minimal test case (no beans)
public class TestUtil { @Test public void testSomething() throws Exception { Asset.assertNotNull("something", "something else"); } }
Test Case with one Bean and no other Bean dependencies
This will create one Bean called ApplicationContext - any Bean dependencies it has will need to be declared inside the TestConfig (as required by Spring)
@RunWith( SpringJUnit4ClassRunner.class ) @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) @ContextConfiguration( classes = TestUtil.TestConfig.class ) public class TestUtil { @Autowired private SampleBean sampleBean; @Test public void testSomething() throws Exception { Assert.assertNotNull( sampleBean ); } @Configuration protected static class TestConfig { @Bean public SampleBean aSampleBean(){ SampleBean bean = new SampleBeanImpl(); bean.setProperty( "Hello World" ); return bean ; } } }
It is important to use @DirtiesContext so the @Bean is destroyed after the Test is done
Test Case with one Bean and other Bean dependencies
This will create 3 Beans and because ServiceWithDependantServices instantiates a concrete class, we need to mock all the dependant services of this class also.
@RunWith( SpringJUnit4ClassRunner.class ) @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) @ContextConfiguration( classes = TestUtil.TestConfig.class ) public class TestUtil { @Autowired private ServiceWithDependantServices serviceWithDependantServices; @Test public void testSomething() throws Exception { Assert.assertNotNull( serviceWithDependantServices ); } @Configuration protected static class TestConfig { @Bean public ServiceWithDependantServices heavyService(){ return new ServiceWithDependantServicesImpl(); } @Bean public SomeDependantService someService() { return mock( SomeDependantService.class ); } @Bean public AnotherDependantService someOtherService() { return mock( AnotherDependantService.class ); } } }
A possible workaround is to mark the @Autowired dependency as not required, you can do this by programmatically like so
@RunWith( SpringJUnit4ClassRunner.class ) @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) @ContextConfiguration( classes = TestUtil.TestConfig.class ) public class TestUtil { @Autowired private ServiceWithDependantServices serviceWithDependantServices; @Test public void testSomething() throws Exception { Assert.assertNotNull( serviceWithDependantServices ); } protected static class TestConfig { @Bean public ServiceWithDependantServices heavyService( AutowiredAnnotationBeanPostProcessor b ){ b.setRequiredParameterValue( false ); return new ServiceWithDependantServicesImpl(); } } }
Note: This will nullpointer when any @Autowired services inside ServiceWithDependantServices are called, because they won't be @Autowired - not exactly what you want either
Scenarios using MockedLoader
The first scenario is the same as the previous - except that all @Autowired dependencies from ServiceWithDependantServicesImpl will be automatically mocked.
@RunWith( SpringJUnit4ClassRunner.class ) @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) @ContextConfiguration( loader = MockedLoader.class, classes = TestUtil.TestConfig.class ) public class TestUtil { @Autowired private SomeDependantService thisWillBeMockedByMockedTestConfig; @Test public void testSomething() throws Exception { Assert.assertNotNull( serviceWithDependantServices ); } protected static class TestConfig { @Bean public ServiceWithDependantServices heavyService(){ return new ServiceWithDependantServicesImpl(); } } }
And in it's simplest case you can use MockedLoader without a custom TestConfig
@RunWith( SpringJUnit4ClassRunner.class ) @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) @ContextConfiguration( loader = MockedLoader.class ) public class TestUtil { @Autowired private ServiceWithDependantServices thisWillBeAutomaticallyMockedByMockedLoader; @Test public void testSomething() throws Exception { Asset.assertNotNull("something", thisWillBeAutomaticallyMockedByMockedLoader.doSomething() ); } }
Download
MockedLoader is part of commons-test and can be included from Maven.