mcustiel/phiremock-codeception-module
A Codeception module that connects your tests to a Phiremock server, letting you create, load, and reset HTTP expectations semantically through the Codeception actor. Supports configurable host, port, secure connections, and per-test reset.
Minimal setup
Install as a dev dependency alongside the Phiremock PHP client (the module delegates to it):
composer require --dev mcustiel/phiremock-codeception-module
Older READMEs pin guzzlehttp/guzzle:^6.0; on a modern Laravel app you will usually already have Guzzle 7, so let Composer resolve it and only constrain if you hit a conflict.
Run the Phiremock server itself — the module is only a client. Start it separately (Composer-installed server / phar / Docker image) before Codeception boots:
vendor/bin/phiremock --port 8086 --host 0.0.0.0
Enable the module in the relevant Codeception suite (tests/Acceptance.suite.yml or tests/Functional.suite.yml):
modules:
enabled:
- Phiremock:
host: localhost # default
port: 8086 # default
reset_before_each_test: true
- Laravel5: # or your framework module
...
Run a suite as usual (vendor/bin/codecept run acceptance).
Where to look first
src/Module.php in the package — that is the complete list of actor methods ($I->...) you can call. It is small; read the whole file.mcustiel/phiremock-client API (the on()/respond() DSL and Expectation/Request value objects) for anything the module does not expose directly.First use case
Testing a Laravel service class or controller that calls a third-party HTTP API. Point that integration's base URL at the Phiremock host/port via an env var, define a stub in the test, then exercise the code under test and assert both the response and that the outbound call actually happened.
public function testCheckoutCallsPaymentGateway(AcceptanceTester $I)
{
$I->haveHttpStubFor('POST', '/v1/charges', [
'statusCode' => 200,
'body' => '{"id":"ch_123","status":"succeeded"}',
'headers' => ['Content-Type' => 'application/json'],
]);
$I->amOnPage('/checkout');
$I->submitForm('#checkout-form', ['card' => '4242...']);
$I->see('Payment accepted');
$I->seeHttpRequest('POST', '/v1/charges'); // verify the outbound call happened
}
1. Stub per test, clean between tests
The single most important pattern is deterministic isolation. Either set reset_before_each_test: true in the suite config, or call the reset explicitly in _before / at the top of each test:
$I->haveACleanSetupInRemoteService();
That clears both the registered expectations and the recorded request journal. If you skip it, stubs and request history leak across tests and order-dependent failures appear.
2. Point the application at the mock, not the real service
In config/services.php (or wherever the external API base URL lives), bind it to an env var and set that var in tests/acceptance/.env.testing / phpunit env:
// config/services.php
'payments' => [
'base_url' => env('PAYMENTS_BASE_URL', 'https://api.real-payments.com'),
],
# tests/acceptance/.env.testing
PAYMENTS_BASE_URL=http://localhost:8086
This keeps the Laravel code under test untouched and makes the integration switch a pure configuration concern.
3. Simulate failure modes explicitly
Because Phiremock lets you control status, body, headers, and delay, model the unhappy paths as first-class tests rather than skipping them:
$I->haveHttpStubFor('GET', '/v1/accounts/42', [
'statusCode' => 503,
'body' => '{"error":"unavailable"}',
'delayMillis' => 5000, // exercise your timeout / retry logic
]);
Then assert your app's error handling (flash message, queued retry, circuit-breaker fallback) rather than just the 200 path.
4. Request verification as a contract check
Use the see/don't-see request assertions to verify your outbound calls — this catches regressions where you silently stop sending a field or change an endpoint:
$I->seeHttpRequest('POST', '/v1/charges');
$I->dontSeeHttpRequest('POST', '/v1/refunds');
Keep these close to the action they verify; spreading them across tests usually means your setup is hiding a flow.
5. Arrange stubs in a Codeception Helper
For a domain with many external endpoints (payments, shipping, tax), wrap repeated stubs in tests/_support/Helper/Acceptance.php:
public function stubPaymentSucceeded(): void
{
$this->getModule('Phiremock')->haveHttpStubFor('POST', '/v1/charges', [...]);
}
This keeps tests readable and gives you a single place to change response shapes when the vendor's API evolves.
6. Go direct to the client for advanced matching
The module exposes the common path; for body/regex/header matching, priorities, or scenarios (stateful sequences), use the underlying Phiremock client via the module's connection, or inject the client into your Helper. This is the escape hatch when haveHttpStubFor is too coarse.
7. Suite placement
Put these in functional/acceptance suites that actually execute HTTP to the mock. Do not try to use them from unit tests — there is no live server in the unit process.
The server is not managed by the module. There is no auto-start, no teardown, no port check. If Phiremock is not running, you get connection-refused errors that look like test failures. In CI, run it as a prior step (or a Compose service) and add a healthcheck hitting the admin endpoint before running Codeception.
Defaults bite. Host defaults to localhost, port to 8086. Your app env must match exactly, including http:// vs https:// and the path prefix. A mismatch produces a real network call to a real host (or DNS failure) rather than a stub hit — and then your seeHttpRequest assertion fails for the wrong reason.
reset_before_each_test defaults to false. This is the trap. Set it to true for any suite with more than one test touching the mock, or your second test inherits the first test's expectations and journal.
URL matching is literal. Include the leading slash, the full path, and be conscious of query strings. If your code appends ?expand=all, stub or match accordingly. A stub for /api/users will not necessarily match /api/users/. When a stub "isn't matching," curl the Phiremock admin endpoint (/__phiremock/expectations and /__phiremock/requests) to see exactly what was registered versus what arrived.
Debug by inspecting the journal, not by guessing. getRequests() / the /__phiremock/requests endpoint shows the method, path, headers, and body Phiremock actually received. This resolves 90% of "why didn't my stub fire" questions — usually it is a path or header mismatch.
Delay is your timeout lever. Use the per-response delay to exercise timeouts, retries, and queued-job backoff. Keep delays small enough not to slow the suite; a 5s delay on one test is fine, on fifty it is not.
GPL-3.0 license. It is a dev-only dependency and not distributed with your app, but if your organisation has strict license policies or you ship a packaged product, flag it to legal before adopting. This is a real adoption blocker for some teams and worth raising up front.
Version drift in docs. The README's Guzzle 6 constraint reflects an older release; the package is actively released. Read src/Module.php for the exact actor method signatures on the version you installed rather than trusting blog posts — method names have changed across versions.
Extension points. Three levels, in increasing order of effort: (1) Codeception Helper wrappers for domain stubs; (2) a custom module extending Phiremock's module and adding semantic methods; (3) bypassing the module and using mcustiel/phiremock-client directly in hooks for scenario/priority-heavy cases. Start at (1) and only escalate if you genuinely need custom matching.
Keep the mock server out of your committed config. Bind host/port through env vars so local devs can run a shared Phiremock instance on a different port than CI without editing suite YAML.
How can I help you explore Laravel packages today?