Software versions
Please provide at least OS and version of pact-js
- OS: Ubuntu 22
- Consumer Pact library: v16.3.0_
- Provider Pact library: N/A
- Node Version: v22.22.0
- Test runner: Vitest 4.1.1, default threads pool
Issue Checklist
Please confirm the following:
Expected behaviour
mockServerMatchedSuccessfully() should return true when the mock server has received and responded to the expected request, regardless of system load.
Actual behaviour
After upgrading from @pact-foundation/pact 16.2.0 to 16.3.0, Pact contract tests that use the V4 addInteraction().executeTest() pattern intermittently fail with "Mock server failed: expected request not received." The HTTP request is actually sent to the mock server and a correct 200 response is returned to the client, but mockServerMatchedSuccessfully() reports that the request was never received.
The issue only manifests when the Pact test file runs in parallel with other test files that create CPU pressure. It does not occur when the Pact test file runs in isolation. Downgrading to 16.2.0 eliminates the issue entirely — I ran the full suite 10+ times with zero failures.
Steps to reproduce
I apologise that I cannot provide a minimal standalone reproduction. The issue is inherently dependent on parallel CPU load and is flaky by nature — it requires enough concurrent test files to create sufficient system pressure for the race condition to manifest. In our project, it reproduces reliably with a test file containing ~50 sequential addInteraction().executeTest() calls, run alongside 17+ other test files in parallel via Vitest's thread pool. I was unable to reduce this to a self-contained example because the failure rate is directly correlated with overall system load, making a small repro impractical.
The issue depends on the version: switching between 16.2.0 and 16.3.0 (the only change) reliably toggles between the case where all tests reliably pass and not. When there are failures, it is just a small number of Pact tests that fail (< 10%).
It affects tests using the addInteraction().executeTest() pattern, where each executeTest() creates and destroys a mock server on the same port.
It is triggered by parallel CPU load from other test files — not by interaction between multiple Pact test files. We confirmed this by running the Pact test alongside unrelated UI component tests (no Pact usage) and still reproducing the failure. The frequency of the error seems to correlate with how many other tests are being run alongside.
The failing tests show that the code under test receives a valid 200 response from the mock server, but Pact's post-test verification reports the request was not received.
Running with --fileParallelism=false (sequential file execution) eliminates the issue on 16.3.0.
Given that the JS-level executeTest code path appears unchanged between 16.2.0 and 16.3.0, I suspect the regression is in @pact-foundation/pact-core 19.1.0 — possibly a race condition in the native FFI layer's request tracking where the match is recorded asynchronously and mockServerMismatches()/mockServerMatchedSuccessfully() is called before the recording completes.
Relevant log files
(Redacted Debug log with proprietary info removed, showing shutdown of previous test followed by start of test which fails)
2026-03-24T15:14:14.791034Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server <MOCK_SERVER_ID_A> shutdown - MockServerMetrics { requests: 1, requests_by_path: {"/<ENDPOINT>": 1} }
2026-03-24T15:14:14.791051Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID <MOCK_SERVER_ID_A> - MockServerMetrics { requests: 1, requests_by_path: {"/<ENDPOINT>": 1} }
2026-03-24T15:14:14.791083Z DEBUG ThreadId(01) pact_ffi::plugins: pact_ffi::plugins::pactffi_cleanup_plugins FFI function invoked
2026-03-24T15:14:14.792301Z DEBUG ThreadId(01) pact_ffi::mock_server: pact_ffi::mock_server::pactffi_create_mock_server_for_transport FFI function invoked
2026-03-24T15:14:14.793199Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Received connection from remote <LOCALHOST>:<CLIENT_PORT>
2026-03-24T15:14:14.794000Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request path=/<ENDPOINT>
2026-03-24T15:14:14.794054Z INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request POST /<ENDPOINT>
2026-03-24T15:14:14.794060Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server:
----------------------------------------------------------------------------------------
method: POST
path: /<ENDPOINT>
query: None
headers: Some({"content-type": ["application/json"], "host": ["<LOCALHOST>:<MOCK_PORT>"], ...})
body: Present(19 bytes, application/json) '{"page":1,"size":2}'
----------------------------------------------------------------------------------------
2026-03-24T15:14:14.794094Z DEBUG tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: POST, path: /<ENDPOINT>, query: None, headers: Some({"Content-Type": ["application/json"]}), body: Present(19 bytes, application/json) )
2026-03-24T15:14:14.794918Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: []
2026-03-24T15:14:14.795113Z INFO tokio-runtime-worker pact_mock_server::hyper_server: Request matched, sending response
2026-03-24T15:14:14.795120Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server:
----------------------------------------------------------------------------------------
status: 200
headers: Some({"Content-Type": ["application/json"]})
body: Present(<RESPONSE_SIZE> bytes, application/json) '<REDACTED_JSON_PAYLOAD>'
----------------------------------------------------------------------------------------
2026-03-24T15:14:14.795927Z DEBUG ThreadId(01) pact_ffi::mock_server: pact_ffi::mock_server::pactffi_mock_server_mismatches FFI function invoked
2026-03-24T15:14:14.796025Z DEBUG ThreadId(01) pact_ffi::mock_server: pact_ffi::mock_server::pactffi_mock_server_matched FFI function invoked
2026-03-24T15:14:14.796102Z DEBUG ThreadId(01) pact_ffi::mock_server: pact_ffi::mock_server::pactffi_cleanup_mock_server FFI function invoked
2026-03-24T15:14:14.797757Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server <MOCK_SERVER_ID_B> shutdown - MockServerMetrics { requests: 1, requests_by_path: {"/<ENDPOINT>": 1} }
2026-03-24T15:14:14.797779Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID <MOCK_SERVER_ID_B> - MockServerMetrics { requests: 1, requests_by_path: {"/<ENDPOINT>": 1} }
Later, the actual test failure.
FAIL src/features/transactions/services/TransactionService.test.ts > getTransactions Contract > can choose a 0-indexed page and page size to choose a subset
Error: Test failed for the following reasons:
Mock server failed with the following mismatches:
0) The following request was expected but not received:
Method: POST
Path: /transactions
Headers:
Content-Type: application/json
Body: {"page":1,"size":2}... (19 length)
❯ node_modules/@pact-foundation/src/v4/http/index.ts:78:26
❯ step node_modules/@pact-foundation/pact/src/v4/http/index.js:33:23
❯ Object.next node_modules/@pact-foundation/pact/src/v4/http/index.js:14:53
❯ fulfilled node_modules/@pact-foundation/pact/src/v4/http/index.js:5:58
Software versions
Please provide at least OS and version of pact-js
Issue Checklist
Please confirm the following:
Expected behaviour
mockServerMatchedSuccessfully() should return true when the mock server has received and responded to the expected request, regardless of system load.
Actual behaviour
After upgrading from @pact-foundation/pact 16.2.0 to 16.3.0, Pact contract tests that use the V4 addInteraction().executeTest() pattern intermittently fail with "Mock server failed: expected request not received." The HTTP request is actually sent to the mock server and a correct 200 response is returned to the client, but mockServerMatchedSuccessfully() reports that the request was never received.
The issue only manifests when the Pact test file runs in parallel with other test files that create CPU pressure. It does not occur when the Pact test file runs in isolation. Downgrading to 16.2.0 eliminates the issue entirely — I ran the full suite 10+ times with zero failures.
Steps to reproduce
I apologise that I cannot provide a minimal standalone reproduction. The issue is inherently dependent on parallel CPU load and is flaky by nature — it requires enough concurrent test files to create sufficient system pressure for the race condition to manifest. In our project, it reproduces reliably with a test file containing ~50 sequential addInteraction().executeTest() calls, run alongside 17+ other test files in parallel via Vitest's thread pool. I was unable to reduce this to a self-contained example because the failure rate is directly correlated with overall system load, making a small repro impractical.
The issue depends on the version: switching between 16.2.0 and 16.3.0 (the only change) reliably toggles between the case where all tests reliably pass and not. When there are failures, it is just a small number of Pact tests that fail (< 10%).
It affects tests using the addInteraction().executeTest() pattern, where each executeTest() creates and destroys a mock server on the same port.
It is triggered by parallel CPU load from other test files — not by interaction between multiple Pact test files. We confirmed this by running the Pact test alongside unrelated UI component tests (no Pact usage) and still reproducing the failure. The frequency of the error seems to correlate with how many other tests are being run alongside.
The failing tests show that the code under test receives a valid 200 response from the mock server, but Pact's post-test verification reports the request was not received.
Running with --fileParallelism=false (sequential file execution) eliminates the issue on 16.3.0.
Given that the JS-level executeTest code path appears unchanged between 16.2.0 and 16.3.0, I suspect the regression is in @pact-foundation/pact-core 19.1.0 — possibly a race condition in the native FFI layer's request tracking where the match is recorded asynchronously and mockServerMismatches()/mockServerMatchedSuccessfully() is called before the recording completes.
Relevant log files
(Redacted Debug log with proprietary info removed, showing shutdown of previous test followed by start of test which fails)
Later, the actual test failure.