Cucumber并行执行时Spring Boot中WireMock存根定义失败
问题背景
我有一个Spring Boot应用,使用Cucumber和WireMock做集成测试。单线程执行时测试完全正常,但开启cucumber.execution.parallel.enabled=true切换到并行执行后,测试开始失败,抛出如下JSON解析异常:
com.github.tomakehurst.wiremock.common.JsonException: { "errors" : [ { "code" : 10, "source" : { "pointer" : "/code" }, "title" : "Error parsing JSON", "detail" : "Unrecognized field \"code\" (class com.github.tomakehurst.wiremock.common.Errors), not marked as ignorable" } ] } at com.github.tomakehurst.wiremock.common.JsonException.fromJackson(JsonException.java:53) at com.github.tomakehurst.wiremock.common.Json.read(Json.java:60) at com.github.tomakehurst.wiremock.client.HttpAdminClient.safelyExecuteRequest(HttpAdminClient.java:519) at com.github.tomakehurst.wiremock.client.HttpAdminClient.executeRequest(HttpAdminClient.java:489) at com.github.tomakehurst.wiremock.client.HttpAdminClient.executeRequest(HttpAdminClient.java:466) at com.github.tomakehurst.wiremock.client.HttpAdminClient.addStubMapping(HttpAdminClient.java:146) at com.github.tomakehurst.wiremock.client.WireMock.register(WireMock.java:414) at com.github.tomakehurst.wiremock.client.WireMock.register(WireMock.java:409) at com.github.tomakehurst.wiremock.client.WireMock.givenThat(WireMock.java:115) at com.github.tomakehurst.wiremock.client.WireMock.stubFor(WireMock.java:119)
请问WireMock.stubFor是否为线程安全操作?从异常来看似乎不是。如果它是非线程安全的,有哪些可行的解决方案(比如同步/锁定所有stubFor的执行)?
存根初始化示例
WireMock.stubFor(post(urlPathMatching("/test")) .withRequestBody(equalToJson(..)) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(..)));
WireMock创建方式
WireMockServer wireMock = new WireMockServer(options() .port(8000) .extensions(new ResponseTemplateTransformer(true)) .notifier(new ConsoleNotifier(true))); wireMock.start(); configureFor(8000);
版本信息:com.github.tomakehurst:wiremock-jre8-standalone:2.33.0
问题分析
WireMock的stubFor操作本身不是线程安全的。当多个测试线程同时向同一个WireMock实例注册存根时,会触发内部并发冲突,导致WireMock的JSON处理逻辑混乱,进而抛出字段无法识别的解析异常。
可行解决方案
1. 全局同步锁控制存根注册
在所有调用WireMock.stubFor的代码外层,使用同一个全局锁对象做同步,确保同一时间只有一个线程在注册存根:
private static final Object WIREMOCK_STUB_LOCK = new Object(); // 注册存根时包裹同步块 synchronized (WIREMOCK_STUB_LOCK) { WireMock.stubFor(post(urlPathMatching("/test")) .withRequestBody(equalToJson(..)) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", "application/json") .withBody(..))); }
这种方式实现简单,但会让存根注册阶段变为串行,牺牲部分并行性能。
2. 为每个测试线程分配独立WireMock实例
避免多线程共享同一个WireMock服务,给每个测试线程启动独立的实例并使用随机端口:
// 每个线程启动独立的WireMockServer,用随机端口避免冲突 WireMockServer wireMock = new WireMockServer(options() .dynamicPort() .extensions(new ResponseTemplateTransformer(true)) .notifier(new ConsoleNotifier(true))); wireMock.start(); // 配置当前线程使用该实例的端口 configureFor(wireMock.port());
测试结束后要记得停止当前线程的WireMock实例,避免资源泄漏。这种方式能最大化并行性能,因为每个线程的测试环境完全隔离,不会有存根冲突。
3. 批量注册存根
将多个存根提前整理为列表,一次性批量注册,减少并发操作的次数:
List<StubMapping> stubMappings = Arrays.asList( post(urlPathMatching("/test")) .withRequestBody(equalToJson(..)) .willReturn(aResponse().withStatus(200)) .build(), // 其他存根定义... ); // 批量注册,减少同步块执行次数 synchronized (WIREMOCK_STUB_LOCK) { wireMock.addStubMappings(stubMappings); }
这种方式比单个存根逐个注册的性能更好,能降低同步锁的开销。
4. 升级WireMock版本
你当前使用的2.33.0版本较旧,后续的WireMock 3.x系列对并发场景做了优化,修复了部分线程安全问题。如果项目允许升级依赖,可以尝试切换到较新的稳定版本,可能直接解决问题。
内容的提问来源于stack exchange,提问作者Vasyl Sarzhynskyi

