mock Spring bean, encapsulate spring bean

How to mock Spring bean without Springockito

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

NEW EDIT: As of Spring Boot 1.4.0, faking of Spring Beans is supported natively via annotation @MockBean. Read Spring Boot docs for more info.

OLD EDIT: Here is better example how to mock Spring bean.

I work with Spring several years. But I was always frustrated with how messy can XML configuration become. As various annotations and possibilities of Java configuration were popping up, I started to enjoy programming with Spring. That is why I strongly entourage using Java configuration. In my opinion, XML configuration is suitable only when you need to have visualized Spring Integration or Spring Batch flow. Hopefully Spring Tool Suite will be able to visualize Java configurations for these frameworks also.

One of the nasty aspects of XML configuration is that it often leads to huge XML configuration files. Developers therefore often create test context configuration for integration testing. But what is the purpose of integration testing, when there isn’t production wiring tested? Such integration test has very little value. So I was always trying to design my production contexts in testable fashion.

I except that when you are creating new project / module you would avoid XML configuration as much as possible.  So with Java configuration you can create Spring configuration per module / package and scan them in main context (@Configuration is also candidate for component scanning). This way you can naturally create islands Spring beans. These islands can be easily tested in isolation.

But I have to admit that it’s not always possible to test production Java configuration as is. Rarely you need to amend behavior or spy on certain beans. There is library for it called Springockito (EDIT: Springokito link dissapeared). To be honest I didn’t use it so far, because I always try to design Spring configuration to avoid need for mocking. Looking at Springockito pace of development (EDIT: Springokito link dissapeared) and number of open issues  (EDIT: Springokito link dissapeared), I would be little bit worried to introduce it into my test suite stack. Fact that last release was done before Spring 4 release brings up questions like “Is it possible to easily integrate it with Spring 4?”. I don’t know, because I didn’t try it. I prefer pure Spring approach if I need to mock Spring bean in integration test.

Spring provides @Primary  annotation for specifying which bean should be preferred in the case when two beans with same type are registered. This is handy because you can override production bean with fake bean in integration test. Let’s explore this approach and some pitfalls on examples.

I chose this simplistic / dummy production code structure for demonstration:

@Repository
public class AddressDao {
	public String readAddress(String userName) {
		return "3 Dark Corner";
	}
}

@Service
public class AddressService {
    private AddressDao addressDao;
    
    @Autowired
    public AddressService(AddressDao addressDao) {
        this.addressDao = addressDao;
    }
    
    public String getAddressForUser(String userName){
        return addressDao.readAddress(userName);
    }
}

@Service
public class UserService {
    private AddressService addressService;

    @Autowired
    public UserService(AddressService addressService) {
        this.addressService = addressService;
    }
    
    public String getUserDetails(String userName){
        String address = addressService.getAddressForUser(userName);
        return String.format("User %s, %s", userName, address);
    }
}

AddressDao singleton bean instance is injected into AddressService. AddressService is similarly used in UserService.

I have to warn you at this stage. My approach is slightly invasive to production code. To be able to fake existing production beans, we have to register fake beans in integration test. But these fake beans are usually in the same package sub-tree as production beans (assuming you are using standard Maven files structure: “src/main/java” and “src/test/java”). So when they are in the same package sub-tree, they would be scanned during integration tests. But we don’t want to use all bean fakes in all integration tests. Fakes could break unrelated integration tests. So we need to have mechanism, how to tell the test to use only certain fake beans. This is done by excluding fake beans from component scanning completely. Integration test explicitly define which fake/s are being used (will show this later). Now let’s take a look at mechanism of excluding fake beans from component scanning. We define our own marker annotation:

public @interface BeanMock {
}

And exclude @BeanMock annotation from  component scanning in main Spring configuration.

@Configuration
@ComponentScan(excludeFilters = @Filter(BeanMock.class))
@EnableAutoConfiguration
public class Application {
}

Root package of component scan is current package of Application class. So all above production beans needs to be in same package or sub-package. We are now need to create integration test for UserService. Let’s spy on address service bean. Of course such testing doesn’t  make practical sense with this production code, but this is just example. So here is our spying bean:

@Configuration
@BeanMock
public class AddressServiceSpy {
	@Bean
	@Primary
	public AddressService registerAddressServiceSpy(AddressService addressService) {
		return spy(addressService);
	}
}

Production AddressService bean is autowired from production context, wrapped into Mockito‘s spy and registered as primary bean for AddressService type. @Primary annotation makes sure that our fake bean will be used in integration test instead of production bean. @BeanMock annotation ensures that this bean can’t be scanned by Application component scanning. Let’s take a look at the integration test now:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, AddressServiceSpy.class })
public class UserServiceITest {
    @Autowired
    private UserService userService;

    @Autowired
    private AddressService addressService;

    @Test
    public void testGetUserDetails() {
        // GIVEN - spring context defined by Application class

        // WHEN
        String actualUserDetails = userService.getUserDetails("john");

        // THEN
        Assert.assertEquals("User john, 3 Dark Corner", actualUserDetails);
        verify(addressService, times(1)).getAddressForUser("john");
    }
}

@SpringApplicationConfigration annotation has two parameters. First (Application.class) declares Spring configuration under test. Second parameter (AddressServiceSpy.class) specifies fake bean that will be loaded for our testing into Spring IoC container. It’s obvious that we can use as many bean fakes as needed, but you don’t want to have many bean fakes. This approach should be used rarely and if you observe yourself using such mocking often, you are probably having serious problem with tight coupling in your application or within your development team in general. TDD methodology should help you target this problem. Bear in mind: “Less mocking is always better!”. So consider production design changes that allow for lower usage of mocks. This applies also for unit testing.

Within integration test we can autowire  this spy bean and use it for various verifications. In this case we verified if testing method userService.getUserDetails called method addressService.getAddressForUser with parameter “john”.

I have one more example. In this case we wouldn’t spy on production bean. We will mock it:

@Configuration
@BeanMock
public class AddressDaoMock {
	@Bean
	@Primary
	public AddressDao registerAddressDaoMock() {
		return mock(AddressDao.class);
	}
}

Again we override production bean, but this time we replace it with Mockito’s mock. We can than record behavior for mock in our integration test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, AddressDaoMock.class })
public class AddressServiceITest {
	@Autowired
	private AddressService addressService;

	@Autowired
	private AddressDao addressDao;

	@Test
	public void testGetAddressForUser() {
		// GIVEN
		when(addressDao.readAddress("john")).thenReturn("5 Bright Corner");

		// WHEN
		String actualAddress = addressService.getAddressForUser("john");

		// THEN
		Assert.assertEquals("5 Bright Corner", actualAddress);
	}

	@After
	public void resetMock() {
		reset(addressDao);
	}
}

We load mocked bean via @SpringApplicationConfiguration‘s parameter. In test method, we stub addressDao.readAddress method to return “5 Bright Corner” string when “john” is passed to it as parameter.

But bear in mind that recorded behavior can be carried to different integration test via Spring context. We don’t want tests affecting each other. So you can avoid future problems in your test suite by reseting mocks after test. This is done in method resetMock.

Source code is on Github.

 

 

 

twittergoogle_plusrss

7 thoughts on “How to mock Spring bean without Springockito

  1. Great article, thanks Lubos! You mentioned your fake beans are a bit invasive, and you use a @BeanMock annotation to exclude them. Would you consider updating your article to use a Spring profile instead?

    If you mark the beans with @Profile("integration-test"), for example, they’d only get loaded when that profile is activated by the user.

    This would safer, as the default behaviour would be to ignore the fake beans. The user does not have to remember to add the excludeFilters to the component scan.

    This approach might even shorten your example too 🙂

    1. What if you have a bunch of test classes and some of them need mocked beans and some don’t.

      E.G. you have a “ServiceBean” class and two tests TestA and TestB. TestA needs real ServiceBean dependency and TestB wants to use mocked ServiceBean. These two test classes should be run during maven build.

      Of course you need to define configuration for mocked ServiceBean for TestB. And thus @ComponentScan will register that mocked bean if we don’t use “excludeFilters”.

      How the same can be achieved with @Profile?

      thanks! 🙂

      1. I am planning to write some blog post/s about this in future, but don’t have time right now.

        But I was using @Profile technique since Colin suggested it. Works nicely, but you need to keep in mind that you can fake @Bean only with fake @Bean and @Component (or @Repository/@Service) only with @Component. You can’t fake @Bean with @Component and vice versa.

        If you want to explore it now, I created a lot of examples where I used @Profile technique for book I am writing. Examples are on Github. So look at this Github search and you can dive into these mechanisms: https://github.com/lkrnac/book-eiws-code-samples/search?utf8=%E2%9C%93&q=Profile&type=Code.

Leave a Reply

Your email address will not be published. Required fields are marked *