Spring Batch 单元测试实战

Spring Batch 单元测试实战

本文基于之前的 SpringBatch 多写示例,展示如何编写针对性的单元测试。本示例不使用 @SpringBootTest,从而避免启动完整服务上下文;同时,为了简化测试流程,我们对 Reader 进行了 Mock。

在本测试示例中,我们使用了以下 Spring 注解和配置:

  • @EnableBatchProcessing:启用 Spring Batch 所需的核心组件。
  • @TestConfiguration:定义测试专用的配置类。
  • @ContextConfiguration:指定测试加载的配置类。
  • @ExtendWith(SpringExtension.class):激活 Spring 测试支持,使得 Spring 上下文能够在测试中加载。

Job单元测试代码示例

下面代码示例,包含对 JobRepository、JobLauncher、StepBuilderFactory 及 JobBuilderFactory 的模拟配置,以及对 Reader 的 Mock 处理:

java 复制代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MultiWriteTestJobConfiguration.class, Job1Test.MockSpringBatchConfig.class})
@EnableBatchProcessing
public class Job1Test {

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job job1;

    @Autowired
    private Job job2;

    @TestConfiguration
    static class MockSpringBatchConfig {

        @Bean
        @Primary
        public JobRepository jobRepository() {
            try {
                return new MapJobRepositoryFactoryBean().getObject();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @Bean
        public JobLauncher jobLauncher(JobRepository jobRepository) {
            SimpleJobLauncher launcher = new SimpleJobLauncher();
            launcher.setJobRepository(jobRepository);
            return launcher;
        }

        @Bean
        public StepBuilderFactory stepBuilderFactory(JobRepository jobRepository) {
            return new StepBuilderFactory(jobRepository, new ResourcelessTransactionManager());
        }

        @Bean
        public JobBuilderFactory jobBuilderFactory(JobRepository jobRepository) {
            return new JobBuilderFactory(jobRepository);
        }
    }

    // 使用 @MockBean 对 Reader 进行 Mock,避免依赖实际数据库数据
    @MockBean(name = "myReader")
    private JdbcPagingItemReader<OrderEntity> myReader;

    @BeforeEach
    public void setUp() throws Exception {
        OrderEntity order1 = new OrderEntity();
        order1.setId(1L);
        order1.setAmount(new BigDecimal("1"));
        order1.setChannelId("WEIXIN");

        OrderEntity order2 = new OrderEntity();
        order2.setId(2L);
        order2.setAmount(new BigDecimal("2"));
        order2.setChannelId("WEIXIN");

        OrderEntity order3 = new OrderEntity();
        order3.setId(3L);
        order3.setAmount(new BigDecimal("3"));
        order3.setChannelId("ZHIFUBAO");

        List<OrderEntity> mockOrders = Arrays.asList(order1, order2, order3);

        // 配置 Mock 行为:依次返回三个 OrderEntity 对象,随后返回 null 表示数据读取完毕
        when(myReader.read()).thenReturn(mockOrders.get(0), mockOrders.get(1), mockOrders.get(2), null);
    }

    @Test
    public void testJob() throws Exception {
        JobExecution jobExecution = jobLauncher.run(job2, new JobParameters());
        assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
    }
}

Reader和Writer的单元测试

Reader 单元测试

java 复制代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestReaderConfig.class})
public class ReaderTest {

    @Autowired
    private JdbcPagingItemReader<OrderEntity> myReader;

    @Autowired
    private DataSource dataSource;

    @BeforeEach
    public void setUpDatabase() throws Exception {
        // 在内存数据库中创建表,并插入测试数据
        try (Connection conn = dataSource.getConnection();
             Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE TABLE order_table (" +
                    "id BIGINT, " +
                    "channel_id VARCHAR(50), " +
                    "order_no VARCHAR(50), " +
                    "amount DECIMAL(10,2)" +
                    ")");
            stmt.execute("INSERT INTO order_table VALUES (1, 'WEIXIN', 'order1', 1.00)");
            stmt.execute("INSERT INTO order_table VALUES (2, 'ZHIFUBAO', 'order2', 2.00)");
            stmt.execute("INSERT INTO order_table VALUES (3, 'WEIXIN', 'order3', 3.00)");
        }
    }

    @Test
    public void testReader() throws Exception {
        ExecutionContext executionContext = new ExecutionContext();
        myReader.open(executionContext);
        List<OrderEntity> orders = new ArrayList<>();
        OrderEntity order;
        while ((order = myReader.read()) != null) {
            orders.add(order);
        }
        myReader.close();

        // 校验读取到的数据条数及部分数据内容
        assertEquals(3, orders.size());
        assertEquals("WEIXIN", orders.get(0).getChannelId());
        assertEquals(new BigDecimal("2.00"), orders.get(1).getAmount());
    }
}

@Configuration
@Import(MultiWriteTestJobConfiguration.class)
class TestReaderConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }
}

Writer 单元测试

java 复制代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestWriterConfig.class})
public class WriterTest {

    @Autowired
    private FlatFileItemWriter<OrderEntity> writer;

    private Path tempFile;

    @BeforeEach
    public void setUp() throws Exception {
        // 创建临时文件作为输出目标
        tempFile = Files.createTempFile("orders_test", ".csv");
        writer.setResource(new FileSystemResource(tempFile.toFile()));
    }

    @Test
    public void testWriter() throws Exception {
        // 准备测试数据
        OrderEntity order = new OrderEntity();
        order.setId(1L);
        order.setChannelId("WEIXIN");
        order.setAmount(new BigDecimal("1"));
        List<OrderEntity> orders = Collections.singletonList(order);

        // 打开 writer 并写入数据
        ExecutionContext executionContext = new ExecutionContext();
        writer.open(executionContext);
        writer.write(orders);
        writer.close();

        // 读取临时文件内容并进行校验
        List<String> lines = Files.readAllLines(tempFile);
        assertFalse(lines.isEmpty());
        String firstLine = lines.get(0);
        assertTrue(firstLine.contains("1"));
        assertTrue(firstLine.contains("WEIXIN"));
        assertTrue(firstLine.contains("1"));
    }
}

@Configuration
@Import(MultiWriteTestJobConfiguration.class)
class TestWriterConfig {

    @Bean
    @Primary
    public FlatFileItemWriter<OrderEntity> testWriter() {
        return new FlatFileItemWriterBuilder<OrderEntity>()
                .name("testOrderItemWriter")
                .resource(new FileSystemResource("target/test-output/orders_test.csv"))
                .delimited()
                .delimiter(",")
                .names("id", "channelId", "amount")
                .build();
    }
}
相关推荐
DuelCode1 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社21 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
幽络源小助理1 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
猴哥源码1 小时前
基于Java+springboot 的车险理赔信息管理系统
java·spring boot
Code blocks3 小时前
使用Jenkins完成springboot项目快速更新
java·运维·spring boot·后端·jenkins
全干engineer9 小时前
Spring Boot 实现主表+明细表 Excel 导出(EasyPOI 实战)
java·spring boot·后端·excel·easypoi·excel导出
a_Dragon19 小时前
Spring Boot多环境开发-Profiles
java·spring boot·后端·intellij-idea
ChinaRainbowSea9 小时前
补充:问题:CORS ,前后端访问跨域问题
java·spring boot·后端·spring
全栈凯哥13 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
RainbowSea15 小时前
跨域问题(Allow CORS)解决(3 种方法)
java·spring boot·后端