RESOLVED - issue BATCH-1481: Support injection of step-scoped dependencies into unit tests

Added StepScopeTestUtils.  Revised a few test cases.
This commit is contained in:
dsyer
2010-01-07 09:33:12 +00:00
parent 919fc0f8ff
commit f8431dff38
11 changed files with 234 additions and 92 deletions

View File

@@ -11,7 +11,7 @@
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="itemReaderParent" class="org.springframework.batch.item.file.FlatFileItemReader" abstract="true">
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
@@ -27,14 +27,11 @@
</property>
</bean>
</property>
<property name="resource" value="#{jobParameters[inputFile]}" />
</bean>
<bean id="itemReader" parent="itemReaderParent" scope="step">
<property name="resource" value="#{jobParameters[fileName]}" />
</bean>
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:./target/test-outputs/delimitedOutput.csv" />
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
<property name="resource" value="#{jobParameters[outputFile]}" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value=","/>

View File

@@ -9,8 +9,8 @@
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="data/iosample/input/fixedLength.txt" />
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="#{jobParameters[inputFile]}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
@@ -31,8 +31,8 @@
</property>
</bean>
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputResource" />
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
<property name="resource" value="#{jobParameters[outputFile]}" />
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.FormatterLineAggregator">
@@ -46,9 +46,5 @@
</bean>
</property>
</bean>
<bean id="outputResource" class="org.springframework.core.io.FileSystemResource">
<constructor-arg value="target/test-outputs/fixedLengthOutput.txt" />
</bean>
</beans>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
@@ -9,32 +8,18 @@
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="itemReaderForTest" class="org.springframework.batch.item.database.JdbcCursorItemReader" scope="prototype">
<property name="dataSource" ref="dataSource"/>
<property name="verifyCursorPosition" value="${batch.verify.cursor.position}"/>
<property name="sql" value="select ID, NAME, CREDIT from CUSTOMER"/>
<property name="rowMapper">
<bean class="org.springframework.batch.sample.domain.trade.internal.CustomerCreditRowMapper"/>
</property>
</bean>
<bean id="itemReader"
class="org.springframework.batch.item.database.JdbcPagingItemReader" autowire-candidate="false"
scope="step">
<bean id="itemReader" class="org.springframework.batch.item.database.JdbcPagingItemReader" scope="step">
<property name="dataSource" ref="dataSource" />
<property name="rowMapper">
<bean
class="org.springframework.batch.sample.domain.trade.internal.CustomerCreditRowMapper" />
<bean class="org.springframework.batch.sample.domain.trade.internal.CustomerCreditRowMapper" />
</property>
<property name="queryProvider">
<bean
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<bean class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="sortKey" value="ID" />
<property name="selectClause" value="select ID, NAME, CREDIT"/>
<property name="selectClause" value="select ID, NAME, CREDIT" />
<property name="fromClause" value="FROM CUSTOMER" />
<property name="whereClause"
value="WHERE CREDIT > :credit" />
<property name="whereClause" value="WHERE CREDIT > :credit" />
</bean>
</property>
<property name="pageSize" value="10" />
@@ -47,15 +32,12 @@
</property>
</bean>
<bean id="itemWriter"
class="org.springframework.batch.item.database.JdbcBatchItemWriter">
<bean id="itemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
<property name="assertUpdates" value="true" />
<property name="itemSqlParameterSourceProvider">
<bean
class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider" />
<bean class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider" />
</property>
<property name="sql"
value="UPDATE CUSTOMER set credit = :credit where id = :id" />
<property name="sql" value="UPDATE CUSTOMER set credit = :credit where id = :id" />
<property name="dataSource" ref="dataSource" />
</bean>

View File

@@ -10,12 +10,14 @@ import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.batch.test.StepScopeTestExecutionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
@@ -29,8 +31,9 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution
*
* @author Robert Kasanicky
*/
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/job-runner-context.xml", "/jobs/ioSampleJob.xml" })
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class})
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/job-runner-context.xml",
"/jobs/ioSampleJob.xml" })
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
public abstract class AbstractIoSampleTests {
@Autowired
@@ -111,4 +114,14 @@ public abstract class AbstractIoSampleTests {
}
}
/**
* Create a {@link StepExecution} that can be used to satisfy step scoped
* dependencies in the test itself (not in the job it launches).
*
* @return a {@link StepExecution}
*/
protected StepExecution getStepExecution() {
return MetaDataInstanceFactory.createStepExecution(getUniqueJobParameters());
}
}

View File

@@ -16,37 +16,40 @@
package org.springframework.batch.sample.iosample;
import java.util.Collections;
import java.util.Map;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dan Garrette
* @author Dave Syer
* @since 2.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/jobs/iosample/delimited.xml")
public class DelimitedFunctionalTests extends AbstractIoSampleTests {
@SuppressWarnings("unused")
private Map<String, String> jobParameters = Collections.singletonMap("fileName",
"file:./target/test-outputs/delimitedOutput.csv");
@Override
protected void pointReaderToOutput(ItemReader<CustomerCredit> reader) {
JobParameters jobParameters = new JobParametersBuilder(super.getUniqueJobParameters()).addString("inputFile",
"file:./target/test-outputs/delimitedOutput.csv").toJobParameters();
StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(jobParameters);
StepSynchronizationManager.close();
StepSynchronizationManager.register(stepExecution);
}
@Override
protected JobParameters getUniqueJobParameters() {
return new JobParametersBuilder(super.getUniqueJobParameters()).addString("fileName",
"data/iosample/input/delimited.csv").toJobParameters();
return new JobParametersBuilder(super.getUniqueJobParameters()).addString("inputFile",
"data/iosample/input/delimited.csv").addString("outputFile",
"file:./target/test-outputs/delimitedOutput.csv").toJobParameters();
}
}

View File

@@ -17,11 +17,13 @@
package org.springframework.batch.sample.iosample;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -29,12 +31,20 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration(locations = "/jobs/iosample/fixedLength.xml")
public class FixedLengthFunctionalTests extends AbstractIoSampleTests {
@Autowired
private Resource outputResource;
@Override
protected void pointReaderToOutput(ItemReader<CustomerCredit> reader) {
FlatFileItemReader<?> fileReader = (FlatFileItemReader<?>) reader;
fileReader.setResource(outputResource);
JobParameters jobParameters = new JobParametersBuilder(super.getUniqueJobParameters()).addString("inputFile",
"file:./target/test-outputs/fixedLengthOutput.txt").toJobParameters();
StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(jobParameters);
StepSynchronizationManager.close();
StepSynchronizationManager.register(stepExecution);
}
@Override
protected JobParameters getUniqueJobParameters() {
return new JobParametersBuilder(super.getUniqueJobParameters()).addString("inputFile",
"data/iosample/input/fixedLength.txt").addString("outputFile",
"file:./target/test-outputs/fixedLengthOutput.txt").toJobParameters();
}
}

View File

@@ -19,13 +19,17 @@ package org.springframework.batch.sample.iosample;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Dan Garrette
* @author Dave Syer
* @since 2.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -34,9 +38,13 @@ public class JdbcPagingFunctionalTests extends AbstractIoSampleTests {
@Override
protected void pointReaderToOutput(ItemReader<CustomerCredit> reader) {
// no-op
JobParameters jobParameters = new JobParametersBuilder(super.getUniqueJobParameters()).addDouble("credit", 0.)
.toJobParameters();
StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(jobParameters);
StepSynchronizationManager.close();
StepSynchronizationManager.register(stepExecution);
}
@Override
protected JobParameters getUniqueJobParameters() {
return new JobParametersBuilder(super.getUniqueJobParameters()).addDouble("credit", 10000.).toJobParameters();

View File

@@ -19,20 +19,25 @@ package org.springframework.batch.sample.iosample;
import static org.junit.Assert.assertEquals;
import java.util.Date;
import java.util.concurrent.Callable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.sample.domain.trade.CustomerCredit;
import org.springframework.batch.test.MetaDataInstanceFactory;
import org.springframework.batch.test.StepScopeTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -49,13 +54,14 @@ public class TwoJobInstancesDelimitedFunctionalTests {
private JobLauncher launcher;
@Autowired
private AbstractJob job;
private Job job;
@Autowired
private FlatFileItemReader<CustomerCredit> reader;
private ItemReader<CustomerCredit> reader;
@Autowired
private Resource outputResource;
@Qualifier("itemReader")
private ItemStream readerStream;
@Test
public void testLaunchJobTwice() throws Exception {
@@ -69,18 +75,31 @@ public class TwoJobInstancesDelimitedFunctionalTests {
private void verifyOutput(int expected) throws Exception {
reader.setResource(outputResource);
reader.open(new ExecutionContext());
JobParameters jobParameters = new JobParametersBuilder().addString("fileName",
"file:./target/test-outputs/delimitedOutput.csv").toJobParameters();
StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(jobParameters);
int count = StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Integer>() {
public Integer call() throws Exception {
int count = 0;
readerStream.open(new ExecutionContext());
try {
while (reader.read() != null) {
count++;
}
}
finally {
readerStream.close();
}
return count;
int count = 0;
try {
while (reader.read() != null) {
count++;
}
}
finally {
reader.close();
}
});
assertEquals(expected, count);

View File

@@ -26,10 +26,10 @@ import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
@@ -49,7 +49,7 @@ public class TwoJobInstancesPagingFunctionalTests {
private JobLauncher launcher;
@Autowired
private AbstractJob job;
private Job job;
private SimpleJdbcTemplate jdbcTemplate;

View File

@@ -22,6 +22,7 @@ import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.PropertiesConverter;
/**
@@ -33,6 +34,31 @@ import org.springframework.batch.support.PropertiesConverter;
*/
public class MetaDataInstanceFactory {
/**
* The default name for a job ("job")
*/
public static final String DEFAULT_JOB_NAME = "job";
/**
* The default id for a job instance (12L)
*/
public static final long DEFAULT_JOB_INSTANCE_ID = 12L;
/**
* The default id for a job execution (123L)
*/
public static final long DEFAULT_JOB_EXECUTION_ID = 123L;
/**
* The default name for a step ("step")
*/
public static final String DEFAULT_STEP_NAME = "step";
/**
* The default id for a step execution (1234L)
*/
public static final long DEFAULT_STEP_EXECUTION_ID = 1234L;
/**
* Create a {@link JobInstance} with the parameters provided.
*
@@ -73,20 +99,20 @@ public class MetaDataInstanceFactory {
/**
* Create a {@link JobInstance} with default parameters.
*
* @return a {@link JobInstance} with name="job", id=12L and empty
* parameters
* @return a {@link JobInstance} with name=DEFAULT_JOB_NAME,
* id=DEFAULT_JOB_INSTANCE_ID and empty parameters
*/
public static JobInstance createJobInstance() {
return new JobInstance(12L, new JobParameters(), "job");
return new JobInstance(DEFAULT_JOB_INSTANCE_ID, new JobParameters(), DEFAULT_JOB_NAME);
}
/**
* Create a {@link JobExecution} with default parameters.
*
* @return a {@link JobExecution} with id=123L
* @return a {@link JobExecution} with id=DEFAULT_JOB_EXECUTION_ID
*/
public static JobExecution createJobExecution() {
return createJobExecution(123L);
return createJobExecution(DEFAULT_JOB_EXECUTION_ID);
}
/**
@@ -96,7 +122,7 @@ public class MetaDataInstanceFactory {
* @return a {@link JobExecution} with valid {@link JobInstance}
*/
public static JobExecution createJobExecution(Long executionId) {
return createJobExecution("job", 12L, executionId);
return createJobExecution(DEFAULT_JOB_NAME, DEFAULT_JOB_INSTANCE_ID, executionId);
}
/**
@@ -144,10 +170,11 @@ public class MetaDataInstanceFactory {
/**
* Create a {@link StepExecution} with default parameters.
*
* @return a {@link StepExecution} with stepName="step" and id=1234L
* @return a {@link StepExecution} with stepName="step" and
* id=DEFAULT_STEP_EXECUTION_ID
*/
public static StepExecution createStepExecution() {
return createStepExecution("step", 1234L);
return createStepExecution(DEFAULT_STEP_NAME, DEFAULT_STEP_EXECUTION_ID);
}
/**
@@ -185,8 +212,8 @@ public class MetaDataInstanceFactory {
* with a unique id
*/
public static JobExecution createJobExecutionWithStepExecutions(Long executionId, Collection<String> stepNames) {
JobExecution jobExecution = createJobExecution("job", 12L, executionId);
Long stepExecutionId = 1234L;
JobExecution jobExecution = createJobExecution(DEFAULT_JOB_NAME, DEFAULT_JOB_INSTANCE_ID, executionId);
Long stepExecutionId = DEFAULT_STEP_EXECUTION_ID;
for (String stepName : stepNames) {
createStepExecution(jobExecution, stepName, stepExecutionId);
stepExecutionId++;
@@ -194,4 +221,46 @@ public class MetaDataInstanceFactory {
return jobExecution;
}
/**
* Create a {@link StepExecution} and all its parent entities with default
* values, but using the {@link ExecutionContext} and {@link JobParameters}
* provided.
*
* @param jobParameters come {@link JobParameters}
* @param executionContext some {@link ExecutionContext}
*
* @return a {@link StepExecution} with the execution context provided
*/
public static StepExecution createStepExecution(JobParameters jobParameters, ExecutionContext executionContext) {
StepExecution stepExecution = createStepExecution(jobParameters);
stepExecution.setExecutionContext(executionContext);
return stepExecution;
}
/**
* Create a {@link StepExecution} and all its parent entities with default
* values, but using the {@link JobParameters} provided.
*
* @param jobParameters some {@link JobParameters}
* @return a {@link StepExecution} with the job parameters provided
*/
public static StepExecution createStepExecution(JobParameters jobParameters) {
JobExecution jobExecution = createJobExecution(DEFAULT_JOB_NAME, DEFAULT_JOB_INSTANCE_ID,
DEFAULT_JOB_EXECUTION_ID, jobParameters);
return jobExecution.createStepExecution(DEFAULT_STEP_NAME);
}
/**
* Create a {@link StepExecution} and all its parent entities with default
* values, but using the {@link ExecutionContext} provided.
*
* @param executionContext some {@link ExecutionContext}
* @return a {@link StepExecution} with the execution context provided
*/
public static StepExecution createStepExecution(ExecutionContext executionContext) {
StepExecution stepExecution = createStepExecution();
stepExecution.setExecutionContext(executionContext);
return stepExecution;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2006-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.test;
import java.util.concurrent.Callable;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
/**
* Utility class for creating and manipulating {@link StepScope} in unit tests.
* This is useful when you want to use the Spring test support and inject
* dependencies into your test case that happen to be step scoped in the
* application context.
*
* @author Dave Syer
*
*/
public class StepScopeTestUtils {
public static <T> T doInStepScope(StepExecution stepExecution, Callable<T> callable) throws Exception {
try {
StepSynchronizationManager.register(stepExecution);
return callable.call();
}
finally {
StepSynchronizationManager.close();
}
}
}