From 9199d0c89544f82d5b7df432c37bc175bd39c7a9 Mon Sep 17 00:00:00 2001 From: Sorin Zamfir Date: Tue, 15 Oct 2019 05:32:04 +0300 Subject: [PATCH] BAEL-3299 - Testing a Spring Batch Job (#7982) * BAEL-3299: First version. Broken tests * BAEL-3299: Fix tests * BAEL-3299: Include gitignore for output files * BAEL-3299: Example of writer unit test * BAEL-3299: Cleaned up and included more tests * BAEL-3299: Updated to use JobParameters * BAEL-3299: Fixed broken startup and included cleanup for tests * BAEL-3299: Fine tuned version. Fixed formatting. * BAEL-3299: Cleaned up redundant stuff * BAEL-3299: Fixed formatting * BAEL-3299: Moved source code in spring-batch module * BAEL-3299: Fixed broken tests --- spring-batch/.gitignore | 3 +- spring-batch/pom.xml | 59 +++++++- .../src/main/java/org/baeldung/batch/App.java | 20 +-- .../org/baeldung/batch/SpringBatchConfig.java | 11 +- .../SpringbatchPartitionerApp.java | 12 +- .../batchtesting/SpringBatchApplication.java | 43 ++++++ .../SpringBatchConfiguration.java | 137 ++++++++++++++++++ .../org/baeldung/batchtesting/model/Book.java | 33 +++++ .../batchtesting/model/BookDetails.java | 50 +++++++ .../batchtesting/model/BookRecord.java | 60 ++++++++ .../service/BookDetailsItemProcessor.java | 24 +++ .../service/BookItemProcessor.java | 22 +++ .../service/BookRecordFieldSetMapper.java | 22 +++ .../batchtesting/application.properties | 3 + .../src/main/resources/batchtesting/input.csv | 8 + spring-batch/src/main/resources/logback.xml | 7 +- .../SpringContextIntegrationTest.java | 6 +- .../java/org/baeldung/SpringContextTest.java | 6 +- .../SpringBatchIntegrationTest.java | 115 +++++++++++++++ .../SpringBatchStepScopeIntegrationTest.java | 116 +++++++++++++++ .../test/resources/input/test-input-one.csv | 1 + .../src/test/resources/input/test-input.csv | 8 + .../test/resources/output/actual-output.json | 3 + .../resources/output/expected-output-one.json | 3 + .../resources/output/expected-output.json | 10 ++ 25 files changed, 750 insertions(+), 32 deletions(-) create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchApplication.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchConfiguration.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/model/Book.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/model/BookDetails.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/model/BookRecord.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/service/BookDetailsItemProcessor.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/service/BookItemProcessor.java create mode 100644 spring-batch/src/main/java/org/baeldung/batchtesting/service/BookRecordFieldSetMapper.java create mode 100644 spring-batch/src/main/resources/batchtesting/application.properties create mode 100644 spring-batch/src/main/resources/batchtesting/input.csv create mode 100644 spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchIntegrationTest.java create mode 100644 spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchStepScopeIntegrationTest.java create mode 100644 spring-batch/src/test/resources/input/test-input-one.csv create mode 100644 spring-batch/src/test/resources/input/test-input.csv create mode 100644 spring-batch/src/test/resources/output/actual-output.json create mode 100644 spring-batch/src/test/resources/output/expected-output-one.json create mode 100644 spring-batch/src/test/resources/output/expected-output.json diff --git a/spring-batch/.gitignore b/spring-batch/.gitignore index 0ef6d10b38..5c16bc1baf 100644 --- a/spring-batch/.gitignore +++ b/spring-batch/.gitignore @@ -1 +1,2 @@ -output.csv \ No newline at end of file +output.csv +output.json \ No newline at end of file diff --git a/spring-batch/pom.xml b/spring-batch/pom.xml index e81078568b..48d3baeae3 100644 --- a/spring-batch/pom.xml +++ b/spring-batch/pom.xml @@ -15,12 +15,30 @@ + + + + + javax.xml.bind + jaxb-api + ${jaxb.version} + runtime + + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb.version} + runtime + + org.xerial sqlite-jdbc ${sqlite.version} + org.springframework spring-oxm @@ -32,40 +50,67 @@ + org.springframework spring-jdbc ${spring.version} + org.springframework.batch spring-batch-core ${spring.batch.version} + org.springframework.batch spring-batch-test ${spring.batch.version} + com.opencsv opencsv ${opencsv.version} + + + org.springframework.boot + spring-boot-starter-batch + ${spring.boot.version} + + + + org.hsqldb + hsqldb + 2.5.0 + runtime + - org.awaitility - awaitility - ${awaitility.version} - test - + org.awaitility + awaitility + ${awaitility.version} + test + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + - 5.0.3.RELEASE - 4.0.0.RELEASE + 5.2.0.RELEASE + 4.2.0.RELEASE + 2.1.9.RELEASE 3.15.1 4.1 + 2.3.1 3.1.1 diff --git a/spring-batch/src/main/java/org/baeldung/batch/App.java b/spring-batch/src/main/java/org/baeldung/batch/App.java index 749591aa03..91b99ba571 100644 --- a/spring-batch/src/main/java/org/baeldung/batch/App.java +++ b/spring-batch/src/main/java/org/baeldung/batch/App.java @@ -1,5 +1,7 @@ package org.baeldung.batch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; @@ -8,6 +10,9 @@ import org.springframework.batch.core.launch.JobLauncher; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + public static void main(final String[] args) { // Spring Java config final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -27,19 +32,16 @@ public class App { final JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); final Job job = (Job) context.getBean(batchJobName); - System.out.println("----------------------------------------"); - System.out.println("Starting the batch job: " + batchJobName); + LOGGER.info("Starting the batch job: {}", batchJobName); try { - // To enable multiple execution of a job with the same parameters - JobParameters jobParameters = new JobParametersBuilder() - .addString("jobID", String.valueOf(System.currentTimeMillis())) - .toJobParameters(); + // To enable multiple execution of a job with the same parameters + JobParameters jobParameters = new JobParametersBuilder().addString("jobID", String.valueOf(System.currentTimeMillis())) + .toJobParameters(); final JobExecution execution = jobLauncher.run(job, jobParameters); - System.out.println("Job Status : " + execution.getStatus()); - System.out.println("Job succeeded"); + LOGGER.info("Job Status : {}", execution.getStatus()); } catch (final Exception e) { e.printStackTrace(); - System.out.println("Job failed"); + LOGGER.error("Job failed {}", e.getMessage()); } } } \ No newline at end of file diff --git a/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java b/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java index b318dda154..07dd65bcfd 100644 --- a/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java +++ b/spring-batch/src/main/java/org/baeldung/batch/SpringBatchConfig.java @@ -86,9 +86,14 @@ public class SpringBatchConfig { } @Bean - protected Step step1(@Qualifier("itemProcessor") ItemProcessor processor, - ItemWriter writer) throws ParseException { - return stepBuilderFactory.get("step1").chunk(10).reader(itemReader(inputCsv)).processor(processor).writer(writer).build(); + protected Step step1(@Qualifier("itemProcessor") ItemProcessor processor, ItemWriter writer) throws ParseException { + return stepBuilderFactory + .get("step1") + . chunk(10) + .reader(itemReader(inputCsv)) + .processor(processor) + .writer(writer) + .build(); } @Bean(name = "firstBatchJob") diff --git a/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java index e56afc591c..f456135058 100644 --- a/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java +++ b/spring-batch/src/main/java/org/baeldung/batch/partitioner/SpringbatchPartitionerApp.java @@ -1,5 +1,7 @@ package org.baeldung.batch.partitioner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; @@ -7,6 +9,9 @@ import org.springframework.batch.core.launch.JobLauncher; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class SpringbatchPartitionerApp { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringbatchPartitionerApp.class); + public static void main(final String[] args) { // Spring Java config final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -15,14 +20,13 @@ public class SpringbatchPartitionerApp { final JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); final Job job = (Job) context.getBean("partitionerJob"); - System.out.println("Starting the batch job"); + LOGGER.info("Starting the batch job"); try { final JobExecution execution = jobLauncher.run(job, new JobParameters()); - System.out.println("Job Status : " + execution.getStatus()); - System.out.println("Job succeeded"); + LOGGER.info("Job Status : {}", execution.getStatus()); } catch (final Exception e) { e.printStackTrace(); - System.out.println("Job failed"); + LOGGER.error("Job failed {}", e.getMessage()); } } } \ No newline at end of file diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchApplication.java b/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchApplication.java new file mode 100644 index 0000000000..39bfdfbee7 --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchApplication.java @@ -0,0 +1,43 @@ +package org.baeldung.batchtesting; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; + +@SpringBootApplication +@PropertySource("classpath:batchtesting/application.properties") +public class SpringBatchApplication implements CommandLineRunner { + + @Autowired + private JobLauncher jobLauncher; + + @Autowired + @Qualifier("transformBooksRecords") + private Job transformBooksRecordsJob; + + @Value("${file.input}") + private String input; + + @Value("${file.output}") + private String output; + + public static void main(String[] args) { + SpringApplication.run(SpringBatchApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + JobParametersBuilder paramsBuilder = new JobParametersBuilder(); + paramsBuilder.addString("file.input", input); + paramsBuilder.addString("file.output", output); + jobLauncher.run(transformBooksRecordsJob, paramsBuilder.toJobParameters()); + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchConfiguration.java b/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchConfiguration.java new file mode 100644 index 0000000000..2d891ad7da --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/SpringBatchConfiguration.java @@ -0,0 +1,137 @@ +package org.baeldung.batchtesting; + +import java.io.IOException; + +import org.baeldung.batchtesting.model.Book; +import org.baeldung.batchtesting.model.BookDetails; +import org.baeldung.batchtesting.model.BookRecord; +import org.baeldung.batchtesting.service.BookDetailsItemProcessor; +import org.baeldung.batchtesting.service.BookItemProcessor; +import org.baeldung.batchtesting.service.BookRecordFieldSetMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.batch.item.file.mapping.FieldSetMapper; +import org.springframework.batch.item.json.JacksonJsonObjectMarshaller; +import org.springframework.batch.item.json.JsonFileItemWriter; +import org.springframework.batch.item.json.builder.JsonFileItemWriterBuilder; +import org.springframework.batch.item.support.ListItemWriter; +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.core.io.FileSystemResource; + + +@Configuration +@EnableBatchProcessing +public class SpringBatchConfiguration { + + private static Logger LOGGER = LoggerFactory.getLogger(SpringBatchConfiguration.class); + + private static final String[] TOKENS = { "bookname", "bookauthor", "bookformat", "isbn", "publishyear" }; + + @Autowired + private JobBuilderFactory jobBuilderFactory; + + @Autowired + private StepBuilderFactory stepBuilderFactory; + + @Bean + @StepScope + public FlatFileItemReader csvItemReader(@Value("#{jobParameters['file.input']}") String input) { + FlatFileItemReaderBuilder builder = new FlatFileItemReaderBuilder<>(); + FieldSetMapper bookRecordFieldSetMapper = new BookRecordFieldSetMapper(); + LOGGER.info("Configuring reader to input {}", input); + // @formatter:off + return builder + .name("bookRecordItemReader") + .resource(new FileSystemResource(input)) + .delimited() + .names(TOKENS) + .fieldSetMapper(bookRecordFieldSetMapper) + .build(); + // @formatter:on + } + + @Bean + @StepScope + public JsonFileItemWriter jsonItemWriter(@Value("#{jobParameters['file.output']}") String output) throws IOException { + JsonFileItemWriterBuilder builder = new JsonFileItemWriterBuilder<>(); + JacksonJsonObjectMarshaller marshaller = new JacksonJsonObjectMarshaller<>(); + LOGGER.info("Configuring writer to output {}", output); + // @formatter:off + return builder + .name("bookItemWriter") + .jsonObjectMarshaller(marshaller) + .resource(new FileSystemResource(output)) + .build(); + // @formatter:on + } + + @Bean + @StepScope + public ListItemWriter listItemWriter() { + return new ListItemWriter(); + } + + @Bean + @StepScope + public BookItemProcessor bookItemProcessor() { + return new BookItemProcessor(); + } + + @Bean + @StepScope + public BookDetailsItemProcessor bookDetailsItemProcessor() { + return new BookDetailsItemProcessor(); + } + + @Bean + public Step step1(ItemReader csvItemReader, ItemWriter jsonItemWriter) throws IOException { + // @formatter:off + return stepBuilderFactory + .get("step1") + . chunk(3) + .reader(csvItemReader) + .processor(bookItemProcessor()) + .writer(jsonItemWriter) + .build(); + // @formatter:on + } + + @Bean + public Step step2(ItemReader csvItemReader, ItemWriter listItemWriter) { + // @formatter:off + return stepBuilderFactory + .get("step2") + . chunk(3) + .reader(csvItemReader) + .processor(bookDetailsItemProcessor()) + .writer(listItemWriter) + .build(); + // @formatter:on + } + + @Bean(name = "transformBooksRecords") + public Job transformBookRecords(Step step1, Step step2) throws IOException { + // @formatter:off + return jobBuilderFactory + .get("transformBooksRecords") + .flow(step1) + .next(step2) + .end() + .build(); + // @formatter:on + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/model/Book.java b/spring-batch/src/main/java/org/baeldung/batchtesting/model/Book.java new file mode 100644 index 0000000000..3ea4a29e7f --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/model/Book.java @@ -0,0 +1,33 @@ +package org.baeldung.batchtesting.model; + +public class Book { + + private String author; + + private String name; + + public Book() { + } + + public String getAuthor() { + return author; + } + + public String getName() { + return name; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Book [author=" + author + ", name=" + name + "]"; + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookDetails.java b/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookDetails.java new file mode 100644 index 0000000000..d9284fbc66 --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookDetails.java @@ -0,0 +1,50 @@ +package org.baeldung.batchtesting.model; + +public class BookDetails { + + private String bookName; + + private String bookFormat; + + private String publishingYear; + + private String bookISBN; + + public String getBookName() { + return bookName; + } + + public void setBookName(String bookName) { + this.bookName = bookName; + } + + public String getBookFormat() { + return bookFormat; + } + + public void setBookFormat(String bookFormat) { + this.bookFormat = bookFormat; + } + + public String getPublishingYear() { + return publishingYear; + } + + public void setPublishingYear(String publishingYear) { + this.publishingYear = publishingYear; + } + + public String getBookISBN() { + return bookISBN; + } + + public void setBookISBN(String bookISBN) { + this.bookISBN = bookISBN; + } + + @Override + public String toString() { + return "BookDetails [bookName=" + bookName + ", bookFormat=" + bookFormat + ", publishingYear=" + publishingYear + ", bookISBN=" + bookISBN + "]"; + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookRecord.java b/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookRecord.java new file mode 100644 index 0000000000..35fb40e878 --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/model/BookRecord.java @@ -0,0 +1,60 @@ +package org.baeldung.batchtesting.model; + +public class BookRecord { + + private String bookName; + + private String bookAuthor; + + private String bookFormat; + + private String bookISBN; + + private String publishingYear; + + public void setBookName(String bookName) { + this.bookName = bookName; + } + + public void setBookAuthor(String bookAuthor) { + this.bookAuthor = bookAuthor; + } + + public void setBookFormat(String bookFormat) { + this.bookFormat = bookFormat; + } + + public void setBookISBN(String bookISBN) { + this.bookISBN = bookISBN; + } + + public void setPublishingYear(String publishingYear) { + this.publishingYear = publishingYear; + } + + public String getBookName() { + return bookName; + } + + public String getBookAuthor() { + return bookAuthor; + } + + public String getBookFormat() { + return bookFormat; + } + + public String getBookISBN() { + return bookISBN; + } + + public String getPublishingYear() { + return publishingYear; + } + + @Override + public String toString() { + return "BookRecord [bookName=" + bookName + ", bookAuthor=" + bookAuthor + ", bookFormat=" + bookFormat + ", bookISBN=" + bookISBN + ", publishingYear=" + publishingYear + "]"; + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookDetailsItemProcessor.java b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookDetailsItemProcessor.java new file mode 100644 index 0000000000..f3800fee51 --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookDetailsItemProcessor.java @@ -0,0 +1,24 @@ +package org.baeldung.batchtesting.service; + +import org.baeldung.batchtesting.model.BookDetails; +import org.baeldung.batchtesting.model.BookRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.item.ItemProcessor; + +public class BookDetailsItemProcessor implements ItemProcessor { + + private static Logger LOGGER = LoggerFactory.getLogger(BookDetailsItemProcessor.class); + + @Override + public BookDetails process(BookRecord item) throws Exception { + BookDetails bookDetails = new BookDetails(); + bookDetails.setBookFormat(item.getBookFormat()); + bookDetails.setBookISBN(item.getBookISBN()); + bookDetails.setPublishingYear(item.getPublishingYear()); + bookDetails.setBookName(item.getBookName()); + LOGGER.info("Processing bookdetails {}", bookDetails); + return bookDetails; + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookItemProcessor.java b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookItemProcessor.java new file mode 100644 index 0000000000..69e72ba1d3 --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookItemProcessor.java @@ -0,0 +1,22 @@ +package org.baeldung.batchtesting.service; + +import org.baeldung.batchtesting.model.Book; +import org.baeldung.batchtesting.model.BookRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.item.ItemProcessor; + +public class BookItemProcessor implements ItemProcessor { + + private static Logger LOGGER = LoggerFactory.getLogger(BookItemProcessor.class); + + @Override + public Book process(BookRecord item) throws Exception { + Book book = new Book(); + book.setAuthor(item.getBookAuthor()); + book.setName(item.getBookName()); + LOGGER.info("Processing book {}", book); + return book; + } + +} diff --git a/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookRecordFieldSetMapper.java b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookRecordFieldSetMapper.java new file mode 100644 index 0000000000..cfc36fed7a --- /dev/null +++ b/spring-batch/src/main/java/org/baeldung/batchtesting/service/BookRecordFieldSetMapper.java @@ -0,0 +1,22 @@ +package org.baeldung.batchtesting.service; + +import org.baeldung.batchtesting.model.BookRecord; +import org.springframework.batch.item.file.mapping.FieldSetMapper; +import org.springframework.batch.item.file.transform.FieldSet; +import org.springframework.validation.BindException; + + +public class BookRecordFieldSetMapper implements FieldSetMapper { + + @Override + public BookRecord mapFieldSet(FieldSet fieldSet) throws BindException { + BookRecord bookRecord = new BookRecord(); + bookRecord.setBookName(fieldSet.readString("bookname")); + bookRecord.setBookAuthor(fieldSet.readString("bookauthor")); + bookRecord.setBookFormat(fieldSet.readString("bookformat")); + bookRecord.setBookISBN(fieldSet.readString("isbn")); + bookRecord.setPublishingYear(fieldSet.readString("publishyear")); + return bookRecord; + } + +} diff --git a/spring-batch/src/main/resources/batchtesting/application.properties b/spring-batch/src/main/resources/batchtesting/application.properties new file mode 100644 index 0000000000..11af6f7d31 --- /dev/null +++ b/spring-batch/src/main/resources/batchtesting/application.properties @@ -0,0 +1,3 @@ +spring.batch.job.enabled=false +file.input=src/main/resources/batchtesting/input.csv +file.output=src/main/resources/batchtesting/output.json \ No newline at end of file diff --git a/spring-batch/src/main/resources/batchtesting/input.csv b/spring-batch/src/main/resources/batchtesting/input.csv new file mode 100644 index 0000000000..5b873241d4 --- /dev/null +++ b/spring-batch/src/main/resources/batchtesting/input.csv @@ -0,0 +1,8 @@ +Foundation,Asimov I.,hardcover,ISBN 12839,2018 +Roadside Picnic,Strugatski A.,paperback,ISBN 12839,1988 +Norwegian Wood,Murakami H.,paperback,ISBN 12839,2015 +Davinci Code,Brown D.,hardcover,ISBN 12839,2005 +Ubik,Dick K. P.,hardcover,ISBN 12839,2013 +JFK,King S.,paperback,ISBN 12839,2017 +Contact,Sagan C.,paperback,ISBN 12839,2000 +Brave New World,Huxley A.,paperback,ISBN 12839,1986 diff --git a/spring-batch/src/main/resources/logback.xml b/spring-batch/src/main/resources/logback.xml index 91d4292b8e..9a622ceb65 100644 --- a/spring-batch/src/main/resources/logback.xml +++ b/spring-batch/src/main/resources/logback.xml @@ -8,14 +8,17 @@ - + + + + + diff --git a/spring-batch/src/test/java/org/baeldung/SpringContextIntegrationTest.java b/spring-batch/src/test/java/org/baeldung/SpringContextIntegrationTest.java index c3a9f74759..0898603083 100644 --- a/spring-batch/src/test/java/org/baeldung/SpringContextIntegrationTest.java +++ b/spring-batch/src/test/java/org/baeldung/SpringContextIntegrationTest.java @@ -5,8 +5,8 @@ import org.junit.Test; public class SpringContextIntegrationTest { - @Test - public final void testMain() throws Exception { + @Test + public void testMain() throws Exception { App.main(null); - } + } } diff --git a/spring-batch/src/test/java/org/baeldung/SpringContextTest.java b/spring-batch/src/test/java/org/baeldung/SpringContextTest.java index 0de9a07b0a..1a4f923a1e 100644 --- a/spring-batch/src/test/java/org/baeldung/SpringContextTest.java +++ b/spring-batch/src/test/java/org/baeldung/SpringContextTest.java @@ -5,8 +5,8 @@ import org.junit.Test; public class SpringContextTest { - @Test - public final void testMain() throws Exception { + @Test + public void testMain() throws Exception { App.main(null); - } + } } diff --git a/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchIntegrationTest.java b/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchIntegrationTest.java new file mode 100644 index 0000000000..d66da89ba4 --- /dev/null +++ b/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchIntegrationTest.java @@ -0,0 +1,115 @@ +package org.baeldung.batchtesting; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Collection; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.test.AssertFile; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +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; +import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + +@RunWith(SpringRunner.class) +@SpringBatchTest +@EnableAutoConfiguration +@ContextConfiguration(classes = { SpringBatchConfiguration.class }) +@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class SpringBatchIntegrationTest { + + private static final String TEST_OUTPUT = "src/test/resources/output/actual-output.json"; + + private static final String EXPECTED_OUTPUT = "src/test/resources/output/expected-output.json"; + + private static final String TEST_INPUT = "src/test/resources/input/test-input.csv"; + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Autowired + private JobRepositoryTestUtils jobRepositoryTestUtils; + + @After + public void cleanUp() { + jobRepositoryTestUtils.removeJobExecutions(); + } + + private JobParameters defaultJobParameters() { + JobParametersBuilder paramsBuilder = new JobParametersBuilder(); + paramsBuilder.addString("file.input", TEST_INPUT); + paramsBuilder.addString("file.output", TEST_OUTPUT); + return paramsBuilder.toJobParameters(); + } + + @Test + public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception { + // given + FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT); + FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT); + + // when + JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters()); + JobInstance actualJobInstance = jobExecution.getJobInstance(); + ExitStatus actualJobExitStatus = jobExecution.getExitStatus(); + + // then + assertThat(actualJobInstance.getJobName(), is("transformBooksRecords")); + assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED")); + AssertFile.assertFileEquals(expectedResult, actualResult); + } + + @Test + public void givenReferenceOutput_whenStep1Executed_thenSuccess() throws Exception { + + // given + FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT); + FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT); + + // when + JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1", defaultJobParameters()); + Collection actualStepExecutions = jobExecution.getStepExecutions(); + ExitStatus actualJobExitStatus = jobExecution.getExitStatus(); + + // then + assertThat(actualStepExecutions.size(), is(1)); + assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED")); + AssertFile.assertFileEquals(expectedResult, actualResult); + } + + @Test + public void whenStep2Executed_thenSuccess() { + + // when + JobExecution jobExecution = jobLauncherTestUtils.launchStep("step2", defaultJobParameters()); + Collection actualStepExecutions = jobExecution.getStepExecutions(); + ExitStatus actualExitStatus = jobExecution.getExitStatus(); + + // then + assertThat(actualStepExecutions.size(), is(1)); + assertThat(actualExitStatus.getExitCode(), is("COMPLETED")); + actualStepExecutions.forEach(stepExecution -> { + assertThat(stepExecution.getWriteCount(), is(8)); + }); + } + +} diff --git a/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchStepScopeIntegrationTest.java b/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchStepScopeIntegrationTest.java new file mode 100644 index 0000000000..f7b888f0f9 --- /dev/null +++ b/spring-batch/src/test/java/org/baeldung/batchtesting/SpringBatchStepScopeIntegrationTest.java @@ -0,0 +1,116 @@ +package org.baeldung.batchtesting; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.baeldung.batchtesting.model.Book; +import org.baeldung.batchtesting.model.BookRecord; +import org.junit.After; +import org.junit.Test; +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.item.file.FlatFileItemReader; +import org.springframework.batch.item.json.JsonFileItemWriter; +import org.springframework.batch.test.AssertFile; +import org.springframework.batch.test.JobRepositoryTestUtils; +import org.springframework.batch.test.MetaDataInstanceFactory; +import org.springframework.batch.test.StepScopeTestUtils; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.core.io.FileSystemResource; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +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; +import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + + +@RunWith(SpringRunner.class) +@SpringBatchTest +@EnableAutoConfiguration +@ContextConfiguration(classes = { SpringBatchConfiguration.class }) +@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class SpringBatchStepScopeIntegrationTest { + + private static final String TEST_OUTPUT = "src/test/resources/output/actual-output.json"; + + private static final String EXPECTED_OUTPUT_ONE = "src/test/resources/output/expected-output-one.json"; + + private static final String TEST_INPUT_ONE = "src/test/resources/input/test-input-one.csv"; + @Autowired + private JsonFileItemWriter jsonItemWriter; + + @Autowired + private FlatFileItemReader itemReader; + + @Autowired + private JobRepositoryTestUtils jobRepositoryTestUtils; + + private JobParameters defaultJobParameters() { + JobParametersBuilder paramsBuilder = new JobParametersBuilder(); + paramsBuilder.addString("file.input", TEST_INPUT_ONE); + paramsBuilder.addString("file.output", TEST_OUTPUT); + return paramsBuilder.toJobParameters(); + } + + @After + public void cleanUp() { + jobRepositoryTestUtils.removeJobExecutions(); + } + + @Test + public void givenMockedStep_whenReaderCalled_thenSuccess() throws Exception { + + // given + StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(defaultJobParameters()); + + // when + StepScopeTestUtils.doInStepScope(stepExecution, () -> { + BookRecord bookRecord; + itemReader.open(stepExecution.getExecutionContext()); + while ((bookRecord = itemReader.read()) != null) { + + // then + assertThat(bookRecord.getBookName(), is("Foundation")); + assertThat(bookRecord.getBookAuthor(), is("Asimov I.")); + assertThat(bookRecord.getBookISBN(), is("ISBN 12839")); + assertThat(bookRecord.getBookFormat(), is("hardcover")); + assertThat(bookRecord.getPublishingYear(), is("2018")); + } + itemReader.close(); + return null; + }); + } + + @Test + public void givenMockedStep_whenWriterCalled_thenSuccess() throws Exception { + + // given + FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT_ONE); + FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT); + Book demoBook = new Book(); + demoBook.setAuthor("Grisham J."); + demoBook.setName("The Firm"); + StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(defaultJobParameters()); + + // when + StepScopeTestUtils.doInStepScope(stepExecution, () -> { + + jsonItemWriter.open(stepExecution.getExecutionContext()); + jsonItemWriter.write(Arrays.asList(demoBook)); + jsonItemWriter.close(); + return null; + }); + + // then + AssertFile.assertFileEquals(expectedResult, actualResult); + } +} diff --git a/spring-batch/src/test/resources/input/test-input-one.csv b/spring-batch/src/test/resources/input/test-input-one.csv new file mode 100644 index 0000000000..2c0a5b1103 --- /dev/null +++ b/spring-batch/src/test/resources/input/test-input-one.csv @@ -0,0 +1 @@ +Foundation,Asimov I.,hardcover,ISBN 12839,2018 \ No newline at end of file diff --git a/spring-batch/src/test/resources/input/test-input.csv b/spring-batch/src/test/resources/input/test-input.csv new file mode 100644 index 0000000000..5b873241d4 --- /dev/null +++ b/spring-batch/src/test/resources/input/test-input.csv @@ -0,0 +1,8 @@ +Foundation,Asimov I.,hardcover,ISBN 12839,2018 +Roadside Picnic,Strugatski A.,paperback,ISBN 12839,1988 +Norwegian Wood,Murakami H.,paperback,ISBN 12839,2015 +Davinci Code,Brown D.,hardcover,ISBN 12839,2005 +Ubik,Dick K. P.,hardcover,ISBN 12839,2013 +JFK,King S.,paperback,ISBN 12839,2017 +Contact,Sagan C.,paperback,ISBN 12839,2000 +Brave New World,Huxley A.,paperback,ISBN 12839,1986 diff --git a/spring-batch/src/test/resources/output/actual-output.json b/spring-batch/src/test/resources/output/actual-output.json new file mode 100644 index 0000000000..1fd6cfcf37 --- /dev/null +++ b/spring-batch/src/test/resources/output/actual-output.json @@ -0,0 +1,3 @@ +[ + {"author":"Grisham J.","name":"The Firm"} +] diff --git a/spring-batch/src/test/resources/output/expected-output-one.json b/spring-batch/src/test/resources/output/expected-output-one.json new file mode 100644 index 0000000000..36c31c6d54 --- /dev/null +++ b/spring-batch/src/test/resources/output/expected-output-one.json @@ -0,0 +1,3 @@ +[ + {"author":"Grisham J.","name":"The Firm"} +] \ No newline at end of file diff --git a/spring-batch/src/test/resources/output/expected-output.json b/spring-batch/src/test/resources/output/expected-output.json new file mode 100644 index 0000000000..32ad8b7ead --- /dev/null +++ b/spring-batch/src/test/resources/output/expected-output.json @@ -0,0 +1,10 @@ +[ + {"author":"Asimov I.","name":"Foundation"}, + {"author":"Strugatski A.","name":"Roadside Picnic"}, + {"author":"Murakami H.","name":"Norwegian Wood"}, + {"author":"Brown D.","name":"Davinci Code"}, + {"author":"Dick K. P.","name":"Ubik"}, + {"author":"King S.","name":"JFK"}, + {"author":"Sagan C.","name":"Contact"}, + {"author":"Huxley A.","name":"Brave New World"} +]