Enterprise Java

How to mock Spring bean without Springockito

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. 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 and number of open issues, 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.

Lubos Krnac

Lubos is a Java/JavaScript developer/Tech Lead and Automation enthusiast. His religion is to constantly improve his developments skills and learn new approaches. He believes TDD drives better design and nicely decoupled code. Past experience includes C++, Assembler and C#.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button