Pact-JVM中为多个Provider设置Mock端口的技术问题
解决Pact多Provider场景下的端口配置问题
方案一:动态适配MockServer随机端口(推荐)
利用Spring Boot的@DynamicPropertySource动态覆盖应用配置,将MockServer的随机端口注入到应用配置中,无需修改静态的application.yml,彻底避免端口冲突问题。
实现步骤
- 在测试类中通过
@PactTestFor关联对应Provider,并注入每个Provider的MockServer实例。 - 使用
@DynamicPropertySource静态方法,将MockServer的实际端口替换掉配置文件中的固定端口值。
代码示例
import au.com.dius.pact.consumer.MockServer; import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.DynamicPropertyRegistry; @ExtendWith(PactConsumerTestExt.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ContractConsumerTest { @Pact(provider = "provider1", consumer = "my-spring-boot-consumer") public RequestResponsePact createProvider1Pact(PactDslWithProvider builder) { return builder .given("provider1 has data") .uponReceiving("request to provider1") .path("/api/provider1/data") .method("GET") .willRespondWith() .status(200) .body("{\"id\": 1, \"name\": \"test1\"}") .toPact(); } @Pact(provider = "provider2", consumer = "my-spring-boot-consumer") public RequestResponsePact createProvider2Pact(PactDslWithProvider builder) { return builder .given("provider2 has data") .uponReceiving("request to provider2") .path("/api/provider2/data") .method("GET") .willRespondWith() .status(200) .body("{\"id\": 2, \"name\": \"test2\"}") .toPact(); } @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry, @PactTestFor(providerName = "provider1") MockServer provider1MockServer, @PactTestFor(providerName = "provider2") MockServer provider2MockServer) { registry.add("provider1.base-url", () -> "http://localhost:" + provider1MockServer.getPort()); registry.add("provider2.base-url", () -> "http://localhost:" + provider2MockServer.getPort()); } @Test @PactTestFor(pactMethod = "createProvider1Pact") @PactTestFor(pactMethod = "createProvider2Pact") public void testConsumerCallsBothProviders() { // 调用Spring Boot服务逻辑,会自动使用动态注入的MockServer端口 // 示例:yourService.callProvider1(); yourService.callProvider2(); } }
说明
@DynamicPropertySource会在测试启动时,自动将两个MockServer的随机端口替换掉application.yml中对应的固定端口配置。- 该方案无需修改静态配置,适配本地开发和CI环境,完全规避端口冲突风险。
方案二:为每个MockServer指定独立固定端口
如果需要和现有静态配置对齐,可为每个Provider的MockServer指定不同的固定端口,确保端口不重复即可避免占用异常。
代码示例
import au.com.dius.pact.consumer.dsl.PactDslWithProvider; import au.com.dius.pact.consumer.junit5.PactConsumerTestExt; import au.com.dius.pact.consumer.junit5.PactTestFor; import au.com.dius.pact.core.model.RequestResponsePact; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; @ExtendWith(PactConsumerTestExt.class) @SpringBootTest public class ContractConsumerTest { @Pact(provider = "provider1", consumer = "my-spring-boot-consumer") public RequestResponsePact createProvider1Pact(PactDslWithProvider builder) { // 契约定义同方案一 } @Pact(provider = "provider2", consumer = "my-spring-boot-consumer") public RequestResponsePact createProvider2Pact(PactDslWithProvider builder) { // 契约定义同方案一 } @Test @PactTestFor(providerName = "provider1", port = "1000") // 指定provider1的MockServer端口为1000 @PactTestFor(providerName = "provider2", port = "2000") // 指定provider2的MockServer端口为2000 public void testConsumerCallsBothProviders() { // 测试逻辑,应用配置的localhost:1000和localhost:2000会直接指向对应MockServer } }
注意事项
- 需确保测试环境中指定的端口未被其他进程占用,否则会抛出端口绑定异常。
- 若使用并行测试,需避免多个测试用例同时占用这些端口,可在测试前后清理端口资源,或在CI环境中使用端口隔离机制。
内容的提问来源于stack exchange,提问作者Kevin de Boer
相关产品推荐
相关产品推荐

