I am writing a simple batch that writes a CSV file, and I wanted to use Spring Batch FlatFileItemWriter
for that, using Spring Boot 2.3.1.RELEASE.
I want to unit test my writer so that I can confirm it's configured properly.
the code is very simple :
public class CSVResultWriter implements ItemWriter<Project> {
private final FlatFileItemWriter writer;
public CSVResultWriter(String outputResource) {
writer=new FlatFileItemWriterBuilder<Project>()
.name("itemWriter")
.resource(new FileSystemResource(outputResource))
.lineAggregator(new PassThroughLineAggregator<>())
.append(true)
.build();
}
@Override
public void write(List<? extends Project> items) throws Exception {
writer.write(items);
}
}
and I am writing a simple unit test without Spring, something like :
File generatedCsvFile = new File(workingDir.toString() + File.separator + "outputData.csv");
CSVResultWriter writer = new CSVResultWriter(generatedCsvFile.getAbsolutePath());
Project sampleProject = Project.builder().name("sampleProject1").build();
writer.write(List.of(sampleProject));
assertThat(generatedCsvFile).exists();
But the test fails saying :
org.springframework.batch.item.WriterNotOpenException: Writer must be open before it can be written to
Looking at Spring source code, I don't understand how it's possible to make it work.. When trying to write items, the first thing that Spring does is checking that the writer is initialized :
@Override
public void write(List<? extends T> items) throws Exception {
if (!getOutputState().isInitialized()) {
throw new WriterNotOpenException("Writer must be open before it can be written to");
}
...
but the way OutputState is built doesn't give a chance to say it has been initiated :
// Returns object representing state.
protected OutputState getOutputState() {
if (state == null) {
File file;
try {
file = resource.getFile();
}
catch (IOException e) {
throw new ItemStreamException("Could not convert resource to file: [" + resource + "]", e);
}
Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
state = new OutputState();
state.setDeleteIfExists(shouldDeleteIfExists);
state.setAppendAllowed(append);
state.setEncoding(encoding);
}
return state;
}
--> the initialized
flag in OutputState
keeps its default value, which is false.
So I am a bit puzzled.. I guess when this is managed by Spring, some magic happens and it works.
Am I missing something obvious, or can't we really test this outside of Spring ?