Spring Batch Unit Testing and Mockito

Jonny Hackett Development Technologies, Java, Spring, Spring Batch, Testing 10 Comments

Attention: The following article was published over 12 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

I have a huge fascination with reality TV shows in which something is created.  My favorite of this genre is the Chip Foose-led “Overhaulin” series that aired on TLC for several seasons. The premise of the show is that Chip and his team take someone’s old muscle car and completely restore the car to show-quality status within a week, and without the owner knowing until the end of the show.  One consistent theme throughout the whole show is that they disassemble an old car and completely rebuild it using updated modern technology.  And while some of that technology used might be quite complicated, the exterior and interior design of the vehicle was always very simple, clean and elegant– which is what Chip Foose designs are well known for.

For the current project I’ve been working on, the analogy to the “Overhaulin” show is fairly similar. We’re in the process of converting a long list of COBOL batch processing applications into java-based batch applications using the Spring Batch framework.  A batch job developed using spring-batch is typically divided up into steps and each step might involve a reader, optional processor and writer. Rather than having an integration test that creates Spring’s application context, then run the job in its entirety and verify the results of the complete job, I wanted to individually test only the necessary Spring components defined for the job at the smallest possible unit of work.  In this case, I only wanted to unit test individually any ItemReader, ItemProcessor or ItemWriter that contained any business logic that I had specifically written for the job.

I was first introduced to Mockito a while back by a colleague of mine at the beginning of the project. Mockito reminded me a little bit of that show “Overhaulin” in regards to a very simple and clean interface for mocking objects and stubbing method calls. Mockito is considered a mocking framework, but according to the Mockito FAQ, it is more accurately a Test Spy framework that allows you to verify behavior and stub methods. For the focus of this discussion I’m going to introduce how to stub method calls in order to unit test Spring components that make use of injected dependencies.

The context of this example will be a fictitious Spring Batch job that will be used to generate Invoices for every active Customer, based on billable time entered by consultants during the given time period. The resulting Invoices will be delivered via email to each Customer. Given this example, we’ll assume that we have an ItemReader that retrieves a list of active customers. For each Customer retrieved we’ll need to create an Invoice, which we’ll implement an ItemProcessor to handle the business logic involved in generating an Invoice.  Here is the source code for that implementation:

public class CustomerInvoiceProcessor implements
		ItemProcessor<Customer, Invoice> {
	private StepExecution stepExecution;

	@Autowired
	private TimeEntryDao timeEntryDao;

	@Autowired
	private EmployeeDao employeeDao;

	@SuppressWarnings("unused")
	@BeforeStep
	private void beforeStep(StepExecution stepExecution) {
		this.stepExecution = stepExecution;
	}
	@Override
	public Invoice process(Customer customer) throws Exception {
		JobParameters jobParams = stepExecution.getJobParameters();
		Date fromDate = jobParams.getDate("fromDate");
		Date toDate = jobParams.getDate("toDate");

		Invoice invoice = new Invoice();
		invoice.setCustomerId(customer.getCustomerId());
		invoice.setCustomerName(customer.getCustomerName());
		invoice.setCustomerEmail(customer.getCustomerEmail());

		List billedTimes =
                        timeEntryDao.findCustomerTimeBilled(
                customer.getCustomerId(), fromDate, toDate);
		int lineNum = 0;

		for (TimeEntry timeEntry : billedTimes) {
			InvoiceItem item = new InvoiceItem();
                        Employee employee =
	                       employeeDao.getById(timeEntry.getEmployeeId());
			item.setEmployeeFirstName(employee.getFirstName());
			item.setEmployeeLastName(employee.getLastName());
			item.setRate(employee.getHourlyRate());
			item.setHours(timeEntry.getHours());
			item.setDescription(timeEntry.getDescription());
			item.setLineNum(lineNum++);
			invoice.getInvoiceItems().add(item);
		}
		return invoice;
	}
}

And the following interfaces, which are the Autowired dependencies defined in the processor:

public interface TimeEntryDao {
	public List findCustomerTimeBilled(
        int customerId, Date fromDate, Date toDate);
}
public interface EmployeeDao {
	public Employee getById(int employeeId);
}

This is a pretty simple example of an implementation of a Spring Batch ItemProcessor that takes a Customer object as input and uses some very simple business logic to create an Invoice object that is returned as its output. In order to fulfill the creation of the invoice, the processor needs to look up the TimeEntry records from the database for the Customer based on the reporting time period. The DAOs used to retrieve that information for the processor are interfaces whose concrete implementations have been injected into the processor by way of spring’s @Autowired annotation. In order for this code to execute properly, these injected dependencies would need to have been instantiated and made available to the processor for execution. And this is where the simplicity and beauty of Mockito comes into the equation.

First, you’ll need to get the Mockito library, which can be downloaded from Mockito’s website: http://code.google.com/p/mockito/.  Or if your project is Mavenized, you can add the following dependency to your project’s pom.xml:


	org.mockito
	mockito-core
	1.9.0
	test

This example also uses the joda-time library, which can be found at http://joda-time.sourceforge.net/ or adding the following to your Maven’s pom.xml:


	joda-time
	joda-time
	2.0

Next you’ll need to create a simple JUnit Test Case for the CustomerInvoiceProcessor class. For this example, we’re using JUnit4 and it should look similar to this:

public class CustomerInvoiceProcessorTest {
    @Before
    public void setUp() throws Exception {
    }
    /**
    * Test method for {@link org.keyhole.batch.CustomerInvoiceProcessor#process(org.keyhole.customer.Customer)}.
    * @throws Exception
    */
    @Test
    public void testProcess() {
        fail("Not yet implemented");
    }
}

Finally, we get to use Mockito– and this is where the coolness begins!  All I really care about for my unit test is the business logic contained within the process method of the CustomerInvoiceProcessor class and that’s where Mockito really shines.  I want to keep this unit test as simple as possible, but I still need to instantiate these injected dependencies.  To do this we’re going to leverage Mockito’s mocking capabilities by utilizing the provided @Mock annotation to specify that the objects should be mocked by Mockito.  For our unit test, the following fields will need to be added to have Mockito create mocked instances of these:

private Customer customer;

@Mock
private TimeEntryDao timeEntryDao;

@Mock
private EmployeeDao employeeDao;

@Mock
protected StepExecution stepExecution;

@Mock
protected JobParameters jobParams;

Now that the DAOs and necessary Spring objects have been mocked, those mocked objects need to be injected into the CustomerInvoiceProcessor using the Mockito provided annotation @InjectMocks.  You’ll need to add the following field to the unit test:

@InjectMocks
private CustomerInvoiceProcessor customerInvoiceProcessor =
        new CustomerInvoiceProcessor();

In addition to the @InjectMocks annotation on the processor under test, you’ll also need to add this to the setUp method of the unit test:

		MockitoAnnotations.initMocks(this);

Now that we have created and injected mocks of the CustomerInvoiceProcessor’s dependencies, we need to stub the method calls of those dependencies as they are used within CustomerInvoiceProcessor using Mockito’s when/given/then approach to stubbing method calls.  Simply stated, given these arguments when calling this method, then return this value. Going back to our example CustomerInvoiceProcessor, the process method accepts a Customer argument and uses the customer’s id to look up any time entries for that customer during the time period provided.  To stub that method call, you’ll need to use Mockito’s when/thenReturn convention for stubbing, which will look like this:

when(this.timeEntryDao.findCustomerTimeBilled(
        1, fromDate.toDate(), toDate.toDate())).thenReturn(createMockTimeEntries());

Simple, yet powerful. When the TimeEntryDao’s findCustomerTimeBilled method is called with these specific arguments, then return this list of TimeEntry objects. Within the createMockTimeEntries method we’re returning a list of hand-crafted objects with only the specific values that will be needed to test the business logic that generates a customer invoice.  What I love most about this approach is that we have successfully decoupled any concrete implementation of the DAOs  and most importantly any test data retrieved from the database that might be volatile over time.  For this example all we have at this point is the CustomerInvoiceProcessor, a few model objects, a couple of DAO interfaces and the CustomerInvoiceProcessorTest. We haven’t even coded the concrete DAOs at this point, yet we were able to adequately test our business logic within the ItemProcessor implementation.

What you finally end up with is also the beginning of utilizing Behavior Driven Development.

For example, if the CustomerInvoiceProcessor had different paths through the business logic based on a value contained within the Customer object that is being referenced, then you would need to create a specific instance of the Customer object with the values necessary to navigate through all paths of the business logic. In addition, any calls to those mock injected DAO’s that require different parameters to return different results they will need to be stubbed for those specific parameters. However, if it doesn’t matter to the CustomerInvoiceProcessor’s logic, Mockito supports several argument matchers such as anyInt(), anyLong(), anyString() and any().  Using those types of argument matchers, the code would look like this:

when(this.timeEntryDao.findCustomerTimeBilled(
    1, fromDate.toDate(), toDate.toDate())).thenReturn(createMockTimeEntries());

Since Spring Batch provides a lot of out-of-the-box implementations for ItemReaders and ItemWriters for most tasks, you’ll more than likely be creating the majority of unit tests to cover the business logic in the ItemProcessors and that’s where you should see the most value.

Even though this blog uses a pretty specific use case for Mockito’s stubbing capabilities, hopefully you’ll see the role it can fulfill in a wide range of applications.

Finally, here’s the full code listing for the JUnit test used for this example:

public class CustomerInvoiceProcessorTest {
    private Customer customer;

    @Mock
    private TimeEntryDao timeEntryDao;

    @Mock
    private EmployeeDao employeeDao;

    @Mock
    protected StepExecution stepExecution;

    @Mock
    protected JobParameters jobParams;

    @InjectMocks
    private CustomerInvoiceProcessor customerInvoiceProcessor =
    new CustomerInvoiceProcessor();

    /**
    * @throws java.lang.Exception
    */
    @Before
    public void setUp() throws Exception {

        MockitoAnnotations.initMocks(this);

        customer = createMockCustomer();

        LocalDate fromDate = LocalDate.parse("2012-01-01");
        LocalDate toDate = LocalDate.parse("2012-01-31");

        // stub in dao calls

        when(this.stepExecution.getJobParameters()).thenReturn(this.jobParams);
        when(this.jobParams.getDate("fromDate")).thenReturn(fromDate.toDate());
        when(this.jobParams.getDate("toDate")).thenReturn(toDate.toDate());

        when(this.timeEntryDao.findCustomerTimeBilled(
            1, fromDate.toDate(), toDate.toDate()))
            .thenReturn(createMockTimeEntries());

        when(this.employeeDao.getById(1))
            .thenReturn(createMockEmployee(1,"John","Doe"));
        when(this.employeeDao.getById(2))
            .thenReturn(createMockEmployee(2,"Jane","Doe"));
    }
    private Customer createMockCustomer() {
        Customer c = new Customer();
        c.setCustomerId(1);
        c.setCustomerEmail("[email protected]");
        c.setCustomerName("Acme Product Co.");
        return c;
    }
    private List; createMockTimeEntries() {
        List entries = new ArrayList();
        entries.add(createMockTimeEntry(
        1,1,LocalDate.parse("2012-01-03").toDate(),8));
        entries.add(createMockTimeEntry(
        1,2,LocalDate.parse("2012-01-05").toDate(),9.5));
        return entries;
    }
    private TimeEntry createMockTimeEntry(int customerId,
        int employeeId, Date entryDate, double hours) {
        TimeEntry t = new TimeEntry();
        t.setCustomerId(customerId);
        t.setEmployeeId(employeeId);
        t.setEntryDate(entryDate);
        t.setHours(hours);
        t.setDescription("Invoice batch processing job and unit tests.");
        return t;
    }
    private Employee createMockEmployee(int i, String firstName,
        String lastName) {
        Employee e = new Employee();
        e.setEmployeeId(i);
        e.setFirstName(firstName);
        e.setLastName(lastName);
        e.setHourlyRate(10.00);
        return e;
    }
    /**
    * Test method for {@link org.keyhole.batch.CustomerInvoiceProcessor#process(org.keyhole.customer.Customer)}.
    * @throws Exception
    */
    @Test
    public void testProcess() throws Exception {
        Invoice invoice = customerInvoiceProcessor.process(customer);
        Assert.notNull(invoice);
        Assert.notEmpty(invoice.getInvoiceItems());
        // any necessary assertions to test processor logic
    }
}

— Jonny Hackett, [email protected]

Spring Batch Blog Series

Part One: Introducing Spring Batch

Part Two:  Getting Started With Spring Batch

Part Three: Generating Large Excel Files Using Spring Batch

Scaling Spring Batch – Step Partitioning

Spring Batch Unit Testing and Mockito

Spring Batch – Replacing XML Job Configuration With JavaConfig

Spring Batch Testing & Mocking Revisited with Spring Boot

References:

0 0 votes
Article Rating
Subscribe
Notify of
guest

10 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments