You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot双微服务流水线端到端测试实现需求咨询

我来帮你一步步实现这个Spring Boot微服务的端到端测试方案,完全贴合你的需求,全程解耦外部依赖(RabbitMQ、生产数据库),保证测试的独立性和稳定性:

1. 创建包含微服务A、B的多模块项目

建议用Maven/Gradle多模块项目来管理两个微服务,这样测试时可以方便地共享配置和依赖:

  • 新建父项目,引入Spring Boot的父依赖
  • 分别创建service-a和service-b子模块:
    • service-a:引入spring-boot-starter-amqp、spring-boot-starter-data-jpa依赖
    • service-b:引入spring-boot-starter-web、spring-boot-starter-amqp、spring-boot-starter-data-jpa依赖

2. 配置测试环境:替换为内存数据库

在两个服务的src/test/resources下创建application-test.yml,配置H2内存数据库替换生产环境的外部DB:

spring:
  datasource:
    url: jdbc:h2:mem:${spring.application.name}-db;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create-drop # 测试结束自动清理数据
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  h2:
    console:
      enabled: true # 可选,方便测试时查看数据库内容

然后在所有测试类上添加@ActiveProfiles("test"),激活这个测试配置。

3. 注入自定义内存MQ,解耦RabbitMQ

核心思路是面向接口编程,定义统一的消息队列接口,生产环境用RabbitMQ实现,测试环境用内存队列实现,Spring会自动根据环境切换:

第一步:定义消息队列接口

public interface MessageQueueHandler {
    // 发送消息到指定队列
    void sendMessage(String queueName, Object payload);
    // 从指定队列接收消息
    <T> T receiveMessage(String queueName, Class<T> payloadType);
}

第二步:生产环境RabbitMQ实现

@Service
@Profile("!test") // 非测试环境生效
public class RabbitMqHandler implements MessageQueueHandler {
    private final RabbitTemplate rabbitTemplate;

    public RabbitMqHandler(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @Override
    public void sendMessage(String queueName, Object payload) {
        rabbitTemplate.convertAndSend(queueName, payload);
    }

    @Override
    public <T> T receiveMessage(String queueName, Class<T> payloadType) {
        return payloadType.cast(rabbitTemplate.receiveAndConvert(queueName));
    }
}

第三步:测试环境内存MQ实现

@Service
@Primary // 测试环境优先注入
@Profile("test") // 仅测试环境生效
public class InMemoryQueueHandler implements MessageQueueHandler {
    // 用ConcurrentHashMap存储不同队列的消息
    private final Map<String, Queue<Object>> queueMap = new ConcurrentHashMap<>();

    @Override
    public void sendMessage(String queueName, Object payload) {
        queueMap.computeIfAbsent(queueName, k -> new ConcurrentLinkedQueue<>()).add(payload);
    }

    @Override
    public <T> T receiveMessage(String queueName, Class<T> payloadType) {
        Queue<Object> queue = queueMap.getOrDefault(queueName, new ConcurrentLinkedQueue<>());
        return payloadType.cast(queue.poll());
    }
}

第四步:改造微服务代码

在A、B服务中,注入MessageQueueHandler而非直接使用RabbitTemplate,比如在服务A的业务类中:

@Service
public class AService {
    private final MessageQueueHandler queueHandler;

    public AService(MessageQueueHandler queueHandler) {
        this.queueHandler = queueHandler;
    }

    public void publishData(DataPayload payload) {
        queueHandler.sendMessage("a-to-b-queue", payload);
    }
}

服务B的消费者逻辑也改为依赖MessageQueueHandler(如果原来用@RabbitListener,可以改为定时拉取或者事件触发,测试环境下避免依赖RabbitMQ的监听容器)。

4. 编写端到端测试

用@SpringBootTest启动两个服务的上下文,结合内存MQ和DB,覆盖从A发消息、B消费写入DB、调用B的REST API的全流程:

测试依赖(可选但推荐)

添加Awaitility库,解决异步消费的等待问题:

<!-- Maven依赖 -->
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>4.2.0</version>
    <scope>test</scope>
</dependency>

端到端测试示例

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    classes = {AServiceApplication.class, BServiceApplication.class}
)
@ActiveProfiles("test")
class FullFlowEndToEndTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private AService aService; // 服务A的业务类

    @Autowired
    private BDataRepository bDataRepository; // 服务B的数据库Repository

    @Test
    void testEndToEndFlow() {
        // 1. 调用服务A的业务方法,发送消息
        DataPayload testPayload = new DataPayload("test-content", 1001);
        aService.publishData(testPayload);

        // 2. 等待服务B消费消息并写入数据库(用Awaitility替代Thread.sleep)
        Awaitility.await()
            .atMost(5, TimeUnit.SECONDS)
            .until(() -> bDataRepository.findByPayloadId(1001).isPresent());

        // 3. 调用服务B的REST API验证结果
        ResponseEntity<BDataResponse> response = restTemplate.getForEntity(
            "/api/b/data/{id}",
            BDataResponse.class,
            1001
        );

        // 4. 断言结果符合预期
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().getContent()).isEqualTo("test-content");
        assertThat(bDataRepository.findByPayloadId(1001).get().getContent()).isEqualTo("test-content");
    }
}

额外注意事项

  • 如果服务B用了@RabbitListener,测试环境下可以用@MockBean禁用RabbitMQ的监听容器,改为手动调用消费逻辑,或者让消费逻辑依赖MessageQueueHandler的receiveMessage方法。
  • 测试前可以用@DirtiesContext保证每次测试的上下文独立性,避免数据污染。
  • 内存MQ可以扩展更多功能,比如消息确认、超时等,根据测试需求调整。

内容的提问来源于stack exchange,提问作者gcasar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:46:00