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();
    }
}
相关推荐
二两小咸鱼儿22 分钟前
Java Demo - JUnit :Unit Test(Assert Methods)
java·后端·junit
热心市民运维小孙24 分钟前
基于HAproxy搭建负载均衡
运维·junit·负载均衡
宝儿65229 分钟前
头歌-Junit实训入门篇
junit
武昌库里写JAVA2 小时前
原生iOS集成react-native (react-native 0.65+)
vue.js·spring boot·毕业设计·layui·课程设计
m0_748230942 小时前
Spring Boot 整合 Redis 步骤详解
spring boot·redis·bootstrap
小杨4045 小时前
springboot框架项目应用实践四(日志)
运维·spring boot·后端
李少兄6 小时前
Spring Boot项目打包第三方Jar包
spring boot·python·jar
葡萄_成熟时_6 小时前
JavaWeb后端基础(8)spring原理
java·spring boot·web
egekm_sefg6 小时前
SpringBoot3 快速启动框架
java·spring boot·后端
m0_748234087 小时前
Spring Boot--@PathVariable、@RequestParam、@RequestBody
java·spring boot·后端