diff --git a/docs/src/site/docbook/reference/index.xml b/docs/src/site/docbook/reference/index.xml
index 8ebfc7cea..e133beb63 100644
--- a/docs/src/site/docbook/reference/index.xml
+++ b/docs/src/site/docbook/reference/index.xml
@@ -44,6 +44,8 @@
+
+
diff --git a/docs/src/site/docbook/reference/testing.xml b/docs/src/site/docbook/reference/testing.xml
new file mode 100644
index 000000000..2d4ca5fd2
--- /dev/null
+++ b/docs/src/site/docbook/reference/testing.xml
@@ -0,0 +1,178 @@
+
+
+
+ Unit Testing
+
+ Just as with other application styles, it is extremely important to
+ unit test any code written as part of a batch job as well. The Spring core
+ documentation covers how to unit and integration test with Spring in great
+ detail, so it won't be repeated here. It is important, however, to think
+ about how to 'end to end' test a batch job, which is what this chapter will
+ focus on.
+
+
+ End To End Testing Batch Jobs
+
+ 'End To End' testing can be defined as testing the complete run of a
+ batch job from beginning to end. If the job reads from a file, then writes
+ into the database, this type of testing ensures that any preconditions are
+ met (reference data, correct file, etc) and then runs the job, verifying
+ afterwards that all records that should be in the database are present and
+ correct. Below is an example from one of the Spring Batch sample jobs, the
+ 'fixedLengthImportJob'. It reads from a flat file (in fixed length format)
+ and loads the records into the database. The following unit test code
+ assures it processes correctly:
+
+ //fixed-length file is expected on input
+ protected void validatePreConditions() throws Exception{
+ BufferedReader reader = null;
+ reader = new BufferedReader(new FileReader(fileLocator.getFile()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ assertEquals (LINE_LENGTH, line.length());
+ }
+ }
+
+ //Check that records have been correctly written to database
+ protected void validatePostConditions() throws Exception {
+
+ inputSource.open(new ExecutionContext());
+
+ jdbcTemplate.query("SELECT ID, ISIN, QUANTITY, PRICE, CUSTOMER FROM trade ORDER BY id", new RowCallbackHandler() {
+
+ public void processRow(ResultSet rs) throws SQLException {
+ Trade trade;
+ try {
+ trade = (Trade)inputSource.read();
+ }
+ catch (Exception e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ assertEquals(trade.getIsin(), rs.getString(2));
+ assertEquals(trade.getQuantity(),rs.getLong(3));
+ assertEquals(trade.getPrice(), rs.getBigDecimal(4));
+ assertEquals(trade.getCustomer(), rs.getString(5));
+ }});
+
+ assertNull(inputSource.read());
+ }
+
+ In the first method, validatePreConditions,
+ the input file is checked to ensure it is correctly formatted. Because it
+ is common to add extra lines to the file to test additional use cases,
+ this test ensures that the fixed length lines are the length they should
+ be. If they are not, it is much preferred to fail in this phase, rather
+ than the job (correctly) failing during the run and causing needless
+ debugging.
+
+ In the second method, validatePostconditions, the database is
+ checked to ensure all data has been written correctly. This is arguably
+ the most important part of the test. In this case, it reads one line from
+ the file, and one row from the database, and checks each column one by one
+ for accuracy. It's important to not hard-code the data that should be
+ present in the database into the test class. Instead, use the input file
+ (bypassing the job) to check the output. This allows you to quickly add
+ additional test cases to your file without having to add them to code. The
+ same would be true for database to database jobs, or database to file
+ jobs. It is preferable to be able to add additional rows to the database
+ input without having to add them to the hard coded list in the test
+ class.
+
+
+
+ Extending Unit Test frameworks
+
+ Because most unit testing of complete batch jobs will take place in
+ the development environment (i.e. eclipse) it's important to be able to
+ launch these tests in the same way you would launch any unit test. In the
+ following examples JUnit 3.8 will be used, but any testing framework could
+ be substituted. The Spring Batch samples contain many 'sample jobs' that
+ are unit tested using this technique. The most important step is being
+ able to launch the job within a unit test. This requires the use of the
+ JobLauncher interface that is discussed in chapters
+ 2 and 4. A Job and
+ JobLauncher must be obtained from an
+ ApplicationContext, and then launched. The
+ following abstract class from Spring Batch Samples illustrates
+ this:
+
+ public abstract class AbstractBatchLauncherTests extends
+ AbstractDependencyInjectionSpringContextTests {
+
+ JobLauncher launcher;
+ private Job job;
+ private JobParameters jobParameters = new JobParameters();
+
+ public AbstractBatchLauncherTests() {
+ setDependencyCheck(false);
+ }
+
+ /*
+ * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigLocations()
+ */
+ protected String[] getConfigLocations() {
+ return new String[] { ClassUtils.addResourcePathToPackagePath(getClass(),
+ ClassUtils.getShortName(getClass()) + "-context.xml") };
+ }
+
+ public void testLaunchJob() throws Exception {
+ launcher.run(job, jobParameters);
+ }
+
+ public void setLauncher(JobLauncher bootstrap) {
+ this.launcher = bootstrap;
+ }
+
+ public void setJob(Job job) {
+ this.job = job;
+ }
+}
+
+ The Spring Test class
+ AbstractDependencyInjectionSpringContextTests is
+ extended to allow for context loading, autowiring, etc. Only two classes
+ are needed: The Job to be run, and the JobLauncher to run it. Empty
+ JobParameters are used in the example above.
+ However, if the job requires specific parameters they could be coded in
+ subclasses with an abstract method, or using a factory bean in the
+ ApplicationContext for testing purposes. Because
+ none of the sample jobs require this, an empty
+ JobParameters is used. One simple JUnit test case
+ is present in the file, which actually launches the job. If any exceptions
+ are thrown or assertions fail, it will act the same way as any other unit
+ test and display as a failed test due to errors or assertion failure.
+ Because of the best practice for validation mentioned earlier in the
+ chapter, this class is extended further to allow for separate validation
+ before and after the job is run:
+
+ public abstract class AbstractValidatingBatchLauncherTests extends AbstractBatchLauncherTests {
+
+ public void testLaunchJob() throws Exception {
+ validatePreConditions();
+ super.testLaunchJob();
+ validatePostConditions();
+ }
+
+ /**
+ * Make sure input data meets expectations
+ */
+ protected void validatePreConditions() throws Exception {}
+
+ /**
+ * Make sure job did what it was expected to do.
+ */
+ protected abstract void validatePostConditions() throws Exception;
+
+}
+
+
+ In the class above, the testLaunchJob
+ method is overridden to call the two abstract methods for validation.
+ Before actually running the job,
+ validatePreConditions is called (it should be
+ noted that it's not required), and then after the job completes
+ successfully, validatePostConidtions is
+ called.
+
+
\ No newline at end of file