How to mock an ItemReader in a Spring Batch application using Spock and Groovy

安稳与你 提交于 2020-07-10 04:48:44

问题


I'm trying to write the tests for a Spring Batch application, specifically on the interaction from the following reader when it gets records from a database implementing a simple RowMapper :

@Component
@StepScope
public class RecordItemReader extends JdbcCursorItemReader<FooDto> {
  @Autowired
  public RecordItemReader(DataSource dataSource) {
    this.setDataSource(dataSource);
    this.setSql(AN_SQL_QUERY);
    this.setRowMapper(new RecordItemMapper());
  }
}

Here is the step definition from the Batch configuration :

  @Bean
  public Step step(RecordItemReader recordItemReader,
                   BatchSkipListener skipListener,
                   RecordItemWriter writer,
                   RecordItemProcessor processor,
                   PlatformTransactionManager transactionManager) {
    return stepBuilderFactory
      .get("step1")
      .transactionManager(transactionManager)
      .reader(recordItemReader)
      .faultTolerant()
      .skip(ParseException.class)
      .skip(UnexpectedInputException.class)
      .skipPolicy(new AlwaysSkipItemSkipPolicy())
      .listener(skipListener)
      .processor(processor)
      .writer(writer)
      .build();
  }

Everything works fine, except when I try to test using the following :

@SpringBatchTest
@EnableAutoConfiguration
class BatchSOTest extends Specification {

  @Resource
  JobLauncherTestUtils jobLauncherTestUtils

  @Resource
  JobRepositoryTestUtils jobRepositoryTestUtils

  @Resource
  RecordItemReader recordItemReader

  def cleanup() {
    jobRepositoryTestUtils.removeJobExecutions()
  }

  def "batch init perfectly"() {
    given:
    // this does not work :
    (1.._) * recordItemReader.read() >> null

    when:
    def jobExecution = jobLauncherTestUtils.launchJob()
    def jobInstance = jobExecution.getJobInstance()
    def exitStatus = jobExecution.getExitStatus()

    then:
    jobInstance.getJobName() == "soJob"
    exitStatus.getExitCode() == ExitStatus.SUCCESS.getExitCode()
  }
}

I'm not able to Mock the reader properly, I tried various ways like updating reader's properties such as MaxRows, but nothing seems to work.

What is the proper way to update the result of the Reader?

Or does it needs to be done another way to properly manipulate the records from the database during unit tests?



UPDATE: Ok so I tried a more structured way using a service inside the reader :

@Component
public class FooDtoItemReader extends AbstractItemStreamItemReader<FooDto> {

  private List<FooDto> foos ;

  private final FooService fooService;

  @Autowired
  public FooDtoItemReader(FooService fooService) {
    this.fooService = fooService;
  }

  @Override
  public void open(ExecutionContext executionContext) {
    try {
      foos = fooService.getFoos();
...
public interface FooService {
  List<FooDto> getFoos();
}
@Service
public class FooServiceImpl implements FooService {

  @Autowired
  private FooDao fooDao;

  @Override
  public List<FooDto> getFoos() {
    return fooDao.getFoos();
  }
}
@Repository
public class FooDaoImpl extends JdbcDaoSupport implements FooDao {

  @Autowired
  DataSource dataSource;

  @PostConstruct
  private void initialize() {
    setDataSource(dataSource);
  }

  @Override
  public List<FooDto> getFoos() {
    return getJdbcTemplate().query(SELECT_SQL, new FooMapper());
  }

}

Here, I'm facing the problem where I cannot mock properly my Service :

I must be missing something with the test utils.

class BatchSOTest extends Specification {

  @Resource
  JobLauncherTestUtils jobLauncherTestUtils

  @Resource
  JobRepositoryTestUtils jobRepositoryTestUtils

  FooService       fooService       = Mock(FooService);
  FooDtoItemReader fooDtoItemReader = new FooDtoItemReader(fooService)

  def cleanup() {
    jobRepositoryTestUtils.removeJobExecutions()
  }

  def "batch init perfectly (second version)"() {
    given:
    fooDtoItemReader.open(Mock(ExecutionContext))

    and:
    // still not working from there :
    (1.._) * fooService.getFoos() >> [createFooEntity(123, "Test")]


    when:
    def jobExecution = jobLauncherTestUtils.launchJob()
    def jobInstance = jobExecution.getJobInstance()
    def exitStatus = jobExecution.getExitStatus()

    then:
    jobInstance.getJobName() == "soJob"
    exitStatus.getExitCode() == ExitStatus.SUCCESS.getExitCode()
  }

But if I try to mock from there, it works :

class FooDtoItemReaderTest extends Specification {

  FooService fooService = Mock(FooService);
  FooDtoItemReader fooDtoItemReader = new FooDtoItemReader(fooService, 0)

  def "open gets the foos and reader is initialized"() {
    given: "Foos examples"
    def foos = [
      createFooEntity(123, "A"),
      createFooEntity(456, "B")
    ]

    when: "reader is initialized"
    fooDtoItemReader.open(Mock(ExecutionContext))

    then: "service get the expected foos"
    1 * fooService.getFoos() >> foos
  }

So what am I doing wrong ?


回答1:


When it comes to testing database interactions, I would not mock the reader. I would instead use an embedded database and populate it with test data. This can be achieved by adding the following bean to your test context:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("/org/springframework/batch/core/schema-drop-h2.sql")
            .addScript("/org/springframework/batch/core/schema-h2.sql")
            .addScript("/schema.sql")
            .addScript("/test-data.sql")
            .build();
}

This example uses H2, but you can use derby or HSLQ or SQLite or any other embeddable database.



来源:https://stackoverflow.com/questions/62212196/how-to-mock-an-itemreader-in-a-spring-batch-application-using-spock-and-groovy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!