Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

Once you have a Spring Batch application written, how do you test it?

This is a follow-on of Spring-Batch – Reading and Writing XML

Testing a Spring Batch Job or a Step

I used the following dependencies to write the Spring Batch tests.

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-test</artifactId>
    <version>${spring.batch.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

Spring Batch provides JobLauncherTestUtils to make it easier to test jobs. The gist of a job test class is:

JobExecution jobExecution = jobLauncherTestUtils.launchJob();

To test a step, you provide the step name to the launchStep method.

JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1")

My Spring test context defined the JobLauncherTestUtils

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath:applicationContext.xml"/>

    <bean id="jobLauncherTestUtils" class="org.springframework.batch.test.JobLauncherTestUtils"/>
</beans>

My Spring Junit test class is fairly simple.

package com.javaninja.batch;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * @author norris.shelton
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestJobAndStep {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void testJob() throws Exception {
        commonAssertions(jobLauncherTestUtils.launchJob());
    }

    @Test
    public void testStep1() throws Exception {
        commonAssertions(jobLauncherTestUtils.launchStep("step1"));
    }

    private void commonAssertions(JobExecution jobExecution) {
        assertNotNull(jobExecution);

        BatchStatus batchStatus = jobExecution.getStatus();
        assertEquals(BatchStatus.COMPLETED, batchStatus);
        assertFalse(batchStatus.isUnsuccessful());

        ExitStatus exitStatus = jobExecution.getExitStatus();
        assertEquals("COMPLETED", exitStatus.getExitCode());
        assertEquals("", exitStatus.getExitDescription());

        List<Throwable> failureExceptions = jobExecution.getFailureExceptions();
        assertNotNull(failureExceptions);
        assertTrue(failureExceptions.isEmpty());
    }
}

Testing Spring Batch Step-scope objects (readers and writers)

Spring Batch provides a StepScopeTestExecutionListener to allow it to inject Step-scope items into your test class via the normal @Autowired.

Readers and writers have three methods that you need to be concerned with.

  • open – Opens the output source.
  • read or write – reads or writes the data, respectively.
  • close – Flushes and closes the output source.

Spring Batch provides a MetaDataInstanceFactory to create a step execution with default parameters.

Spring Batch also provides a StepScopeTestUtils to assist in testing steps with the objects that they would have in scope during their step. To write the test, you will need to implement the doInStepScope method. This method requires a Callable to be implemented. I implemented mine as a Lambda-expression.

Please note that when you call the read method, it will read one item. When you call the write method, it will write the entire collection of provided data.

This all comes together in my test class.

package com.javaninja.batch;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.batch.test.StepScopeTestExecutionListener;
import org.springframework.batch.test.StepScopeTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;

import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * @author norris.shelton
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class})
@ContextConfiguration
public class TestReaderAndWriter {

    @Autowired
    private StaxEventItemReader<Car> itemReader;

    @Autowired
    private StaxEventItemWriter<Car> itemWriter;

    @Test
    public void testReader() {
        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        int count = 0;
        try {
            count = StepScopeTestUtils.doInStepScope(execution, () -> {
                int numCars = 0;
                itemReader.open(execution.getExecutionContext());
                Car car;
                try {
                    while ((car = itemReader.read()) != null) {
                        assertNotNull(car);
                        assertNotNull(car.getMake());
                        assertNotNull(car.getModel());
                        assertNotNull(car.getColor());
                        assertTrue(car.getDoors() > 0);
                        numCars++;
                    }
                } finally {
                    try { itemReader.close(); } catch (ItemStreamException e) { fail(e.toString());
                    }
                }
                return numCars;
            });
        } catch (Exception e) {
            fail(e.toString());
        }
        assertEquals(100000, count);
    }

    @Test
    public void testWriter() throws Exception {
        List<Car> cars = new LinkedList<>();
        Car car;
        for (int i = 1; i < 10001; i++) {
            car = new Car();
            car.setMake("make" + i);
            car.setModel("model" + i);
            car.setColor("color" + i);
            car.setDoors(i);
            cars.add(car);
        }

        StepExecution execution = MetaDataInstanceFactory.createStepExecution();
        StepScopeTestUtils.doInStepScope(execution, () -> {
            itemWriter.open(execution.getExecutionContext());
            itemWriter.write(cars);
            itemWriter.close();
            return null;
        });
    }
}

February 18th, 2016

Posted In: Java, java ninja, Javaninja, JUnit, Spring Batch, Unit Tests

2 Comments

Leave a Reply

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

LinkedIn Auto Publish Powered By : XYZScripts.com