• Marc Vanbrabant

  • Software Engineer

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.

Verwante Artikels