Skip to content

Add a new annotation @SpringBatchTest to simplify testing #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ project('spring-batch-test') {

testCompile "org.apache.commons:commons-dbcp2:$commonsDdbcpVersion"
testCompile "org.hsqldb:hsqldb:$hsqldbVersion"
testCompile "org.mockito:mockito-core:$mockitoVersion"

optional "org.aspectj:aspectjrt:$aspectjVersion"
optional "javax.batch:javax.batch-api:$javaxBatchApiVersion"
Expand Down
56 changes: 48 additions & 8 deletions spring-batch-docs/asciidoc/testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,33 @@ In order for the unit test to run a batch job, the framework must
this behavior:


* `@RunWith(SpringJUnit4ClassRunner.class)`:
* `@RunWith(SpringRunner.class)`:
Indicates that the class should use Spring's JUnit facilities


* `@ContextConfiguration(...)`:
Indicates which resources to configure the `ApplicationContext` with.

The following example shows the two annotations in use:
Starting from v4.1, it is also possible to inject Spring Batch test utilities
like the `JobLauncherTestUtils` and `JobRepositoryTestUtils` in the test context
using the `@SpringBatchTest` annotation.

The following example shows the annotations in use:

.Using Java Configuration
[source, java, role="javaContent"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests { ... }
----

.Using XML Configuration
[source, java, role="xmlContent"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
"/jobs/skipSampleJob.xml" })
public class SkipSampleFunctionalTests { ... }
Expand Down Expand Up @@ -81,7 +87,8 @@ In the following example, the batch job reads from the database and
.XML Based Configuration
[source, java, role="xmlContent"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml",
"/jobs/skipSampleJob.xml" })
public class SkipSampleFunctionalTests {
Expand Down Expand Up @@ -115,7 +122,8 @@ public class SkipSampleFunctionalTests {
.Java Based Configuration
[source, java, role="javaContent"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=SkipSampleConfiguration.class)
public class SkipSampleFunctionalTests {

Expand Down Expand Up @@ -187,15 +195,15 @@ The listener is declared at the class level, and its job is to
@ContextConfiguration
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
StepScopeTestExecutionListener.class })
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
public class StepScopeTestExecutionListenerIntegrationTests {

// This component is defined step-scoped, so it cannot be injected unless
// a step is active...
@Autowired
private ItemReader<String> reader;

public StepExecution getStepExection() {
public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().putString("input.data", "foo,bar,spam");
return execution;
Expand All @@ -222,6 +230,38 @@ There are two `TestExecutionListeners`. One
`StepExecution`). If a factory method is not provided,
then a default `StepExecution` is created.

Starting from v4.1, the `StepScopeTestExecutionListener` and
`JobScopeTestExecutionListener` are imported as test execution listeners
if the test class is annotated with `@SpringBatchTest`. The preceding test
example can be configured as follows:

[source, java]
----
@SpringBatchTest
@RunWith(SpringRunner.class)
@ContextConfiguration
public class StepScopeTestExecutionListenerIntegrationTests {

// This component is defined step-scoped, so it cannot be injected unless
// a step is active...
@Autowired
private ItemReader<String> reader;

public StepExecution getStepExecution() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
execution.getExecutionContext().putString("input.data", "foo,bar,spam");
return execution;
}

@Test
public void testReader() {
// The reader is initialized and bound to the input data
assertNotNull(reader.read());
}

}
----

The listener approach is convenient if you want the duration of the
step scope to be the execution of the test method. For a more flexible
but more invasive approach, you can use the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2018 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.
Expand Down Expand Up @@ -36,6 +36,7 @@
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.repository.dao.AbstractJdbcBatchMetadataDao;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
Expand All @@ -49,6 +50,7 @@
* the transaction.
*
* @author Dave Syer
* @author Mahmoud Ben Hassine
*/
public class JobRepositoryTestUtils extends AbstractJdbcBatchMetadataDao implements InitializingBean {

Expand Down Expand Up @@ -95,6 +97,7 @@ public JobRepositoryTestUtils(JobRepository jobRepository, DataSource dataSource
setDataSource(dataSource);
}

@Autowired
public final void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
Expand All @@ -109,6 +112,7 @@ public void setJobParametersIncrementer(JobParametersIncrementer jobParametersIn
/**
* @param jobRepository the jobRepository to set
*/
@Autowired
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2018 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.context;

import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;

/**
* {@link ContextCustomizer} implementation that adds batch test utility classes
* ({@link JobLauncherTestUtils} and {@link JobRepositoryTestUtils}) as beans in
* the test context.
*
* @author Mahmoud Ben Hassine
* @since 4.1
*/
public class BatchTestContextCustomizer implements ContextCustomizer {

private static final String JOB_LAUNCHER_TEST_UTILS_BEAN_NAME = "jobLauncherTestUtils";
private static final String JOB_REPOSITORY_TEST_UTILS_BEAN_NAME = "jobRepositoryTestUtils";

@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Assert.isInstanceOf(BeanDefinitionRegistry.class, beanFactory,
"The bean factory must be an instance of BeanDefinitionRegistry");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

registry.registerBeanDefinition(JOB_LAUNCHER_TEST_UTILS_BEAN_NAME,
new RootBeanDefinition(JobLauncherTestUtils.class));
registry.registerBeanDefinition(JOB_REPOSITORY_TEST_UTILS_BEAN_NAME,
new RootBeanDefinition(JobRepositoryTestUtils.class));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2018 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.context;

import java.util.List;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;

/**
* Factory for {@link BatchTestContextCustomizer}.
*
* @author Mahmoud Ben Hassine
* @since 4.1
*/
public class BatchTestContextCustomizerFactory implements ContextCustomizerFactory {

@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
SpringBatchTest springBatchTest = AnnotatedElementUtils.findMergedAnnotation(testClass, SpringBatchTest.class);
if (springBatchTest != null) {
return new BatchTestContextCustomizer();
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2018 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.context;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.JobScopeTestExecutionListener;
import org.springframework.batch.test.StepScopeTestExecutionListener;
import org.springframework.test.context.TestExecutionListeners;

/**
* Annotation that can be specified on a test class that runs Spring Batch based tests.
* Provides the following features over the regular <em>Spring TestContext Framework</em>:
* <ul>
* <li>Registers a {@link JobLauncherTestUtils} bean with the
* {@link BatchTestContextCustomizer#JOB_LAUNCHER_TEST_UTILS_BEAN_NAME} which can be used
* in tests for launching jobs and steps.
* </li>
* <li>Registers a {@link JobRepositoryTestUtils} bean
* with the {@link BatchTestContextCustomizer#JOB_REPOSITORY_TEST_UTILS_BEAN_NAME}
* which can be used in tests setup to create or remove job executions.
* </li>
* <li>Registers the {@link StepScopeTestExecutionListener} and {@link JobScopeTestExecutionListener}
* as test execution listeners which are required to test step/job scoped beans.
* </li>
* </ul>
* <p>
* A typical usage of this annotation is like:
*
* <pre class="code">
* &#064;RunWith(SpringRunner.class)
* &#064;SpringBatchTest
* &#064;ContextConfiguration(classes = MyBatchJobConfiguration.class)
* public class MyBatchJobTests {
*
* &#064;@Autowired
* private JobLauncherTestUtils jobLauncherTestUtils;
*
* &#064;@Autowired
* private JobRepositoryTestUtils jobRepositoryTestUtils;
*
* &#064;Before
* public void clearJobExecutions() {
* this.jobRepositoryTestUtils.removeJobExecutions();
* }
*
* &#064;Test
* public void testMyJob() throws Exception {
* // given
* JobParameters jobParameters = this.jobLauncherTestUtils.getUniqueJobParameters();
*
* // when
* JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
*
* // then
* Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
* }
*
* }
* </pre>
*
* @author Mahmoud Ben Hassine
* @since 4.1
* @see JobLauncherTestUtils
* @see JobRepositoryTestUtils
* @see StepScopeTestExecutionListener
* @see JobScopeTestExecutionListener
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@TestExecutionListeners(
listeners = {StepScopeTestExecutionListener.class, JobScopeTestExecutionListener.class},
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
public @interface SpringBatchTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Batch ContextCustomizerFactory implementation for the Spring TestContext Framework
#
org.springframework.test.context.ContextCustomizerFactory= \
org.springframework.batch.test.context.BatchTestContextCustomizerFactory
Loading