Reputation: 2267
I have an ItemProcessor
which has a @BeforeStep
method to access the ExecutionContext
:
public class MegaProcessor implements ItemProcessor<String, String> {
private ExecutionContext context;
@BeforeStep
void getExecutionContext(final StepExecution stepExecution) {
this.context = stepExecution.getExecutionContext();
}
@Override
public String process(final String string) throws Exception {
// ...
}
}
The unit test for this class:
@ContextConfiguration(classes = MegaProcessor.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
@RunWith(SpringRunner.class)
public class MegaProcessorTest {
@Autowired
private MegaProcessor sut;
public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("data", "yeah");
return execution;
}
@Test
public void MegaProcessor() throws Exception {
assertNotNull(sut.process("pew pew"));
}
}
When I debug the test run, context
is null
and the @BeforeStep
method is never called. Why is that and how to achieve that?
Upvotes: 0
Views: 4023
Reputation: 31710
Why is that
If you want to use the StepScopeTestExecutionListener
, the tested component should be step-scoped (See Javadoc). It's not the case in your example. But this is not the real issue. The real issue is that the method annotated with @BeforeStep
will be called before the step in which your processor is registered is executed. In your test case, there is no step running so the method is never called.
how to achieve that?
Since it is a unit test, you can assume the step execution will be passed to your item processor by Spring Batch before running the step and mock/stub it in your unit test. This is how I would unit test the component:
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.StepExecution;
import static org.junit.Assert.assertNotNull;
public class MegaProcessorTest {
private MegaProcessor sut;
@Before
public void setUp() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("data", "yeah");
sut = new MegaProcessor();
sut.getExecutionContext(execution); // I would rename getExecutionContext to setExecutionContext
}
@Test
public void MegaProcessor() throws Exception {
assertNotNull(sut.process("pew pew"));
}
}
The StepScopeTestExecutionListener
is handy when you have step-scoped components that use late-binding to get values from the step execution context. For example:
@Bean
@StepScope
public ItemReader<String> itemReader(@Value("#{stepExecutionContext['items']}") String[] items) {
return new ListItemReader<>(Arrays.asList(items));
}
A unit test of this reader would be something like:
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
@ContextConfiguration(classes = StepScopeExecutionListenerSampleTest.MyApplicationContext.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
@RunWith(SpringRunner.class)
public class StepScopeExecutionListenerSampleTest {
@Autowired
private ItemReader<String> sut;
public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().put("items", new String[] {"foo", "bar"});
return execution;
}
@Test
public void testItemReader() throws Exception {
Assert.assertEquals("foo", sut.read());
Assert.assertEquals("bar", sut.read());
Assert.assertNull(sut.read());
}
@Configuration
static class MyApplicationContext {
@Bean
@StepScope
public ItemReader<String> itemReader(@Value("#{stepExecutionContext['items']}") String[] items) {
return new ListItemReader<>(Arrays.asList(items));
}
/*
* Either declare the step scope like the following or annotate the class
* with `@EnableBatchProcessing` and the step scope will be added automatically
*/
@Bean
public static org.springframework.batch.core.scope.StepScope stepScope() {
org.springframework.batch.core.scope.StepScope stepScope = new org.springframework.batch.core.scope.StepScope();
stepScope.setAutoProxy(false);
return stepScope;
}
}
}
Hope this helps.
Upvotes: 3