如何使用 Spock 和 Groovy 在 Spring 批处理应用程序中模拟 ItemReader
How to mock an ItemReader in a Spring Batch application using Spock and Groovy
我正在尝试为 Spring Batch 应用程序编写 tests,特别是关于来自以下内容的交互reader 当它从实现简单 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());
}
}
这是批处理配置中的步骤定义:
@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();
}
一切正常,除非我尝试使用以下方法进行测试:
@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()
}
}
我无法正确模拟 reader,我尝试了各种方法,例如更新 reader 的属性,例如 MaxRows
,但似乎没有任何效果。
更新Reader结果的正确方法是什么?
或者在单元测试期间,是否需要通过其他方式正确操作数据库中的记录?
更新: 好的,所以我尝试了一种更结构化的方式,使用 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());
}
}
在这里,我遇到了无法正确模拟我的服务的问题:
我一定是遗漏了一些测试工具。
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()
}
但如果我尝试从那里进行模拟,它会起作用:
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
}
那我做错了什么?
在测试数据库交互时,我不会嘲笑 reader。我会改用嵌入式数据库并用测试数据填充它。这可以通过将以下 bean 添加到您的测试上下文来实现:
@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();
}
此示例使用 H2,但您可以使用 derby、HSLQ、SQLite 或任何其他可嵌入的数据库。
我正在尝试为 Spring Batch 应用程序编写 tests,特别是关于来自以下内容的交互reader 当它从实现简单 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());
}
}
这是批处理配置中的步骤定义:
@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();
}
一切正常,除非我尝试使用以下方法进行测试:
@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()
}
}
我无法正确模拟 reader,我尝试了各种方法,例如更新 reader 的属性,例如 MaxRows
,但似乎没有任何效果。
更新Reader结果的正确方法是什么?
或者在单元测试期间,是否需要通过其他方式正确操作数据库中的记录?
更新: 好的,所以我尝试了一种更结构化的方式,使用 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());
}
}
在这里,我遇到了无法正确模拟我的服务的问题:
我一定是遗漏了一些测试工具。
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()
}
但如果我尝试从那里进行模拟,它会起作用:
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
}
那我做错了什么?
在测试数据库交互时,我不会嘲笑 reader。我会改用嵌入式数据库并用测试数据填充它。这可以通过将以下 bean 添加到您的测试上下文来实现:
@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();
}
此示例使用 H2,但您可以使用 derby、HSLQ、SQLite 或任何其他可嵌入的数据库。