A Codeception module for WireMock integration, allowing you to mock HTTP services in your functional tests.
- Create HTTP stubs for any HTTP method (GET, POST, PUT, DELETE, etc.)
- Verify that expected HTTP requests were made
- Advanced request matching (body patterns, headers, query parameters)
- Retrieve request data for debugging
- Automatic cleanup between tests
- Near-miss analysis for debugging failed verifications
- Follows Codeception naming conventions
- PHP 8.2 or higher
- Codeception 5.3 or higher
- A PSR-18 HTTP Client implementation (e.g., Guzzle, Symfony HttpClient)
- A PSR-17 HTTP Factory implementation (e.g., guzzlehttp/psr7)
- A running WireMock server
Install via Composer:
composer require jasonbenett/codeception-module-wiremockThis module depends on PSR-18 (HTTP Client) and PSR-17 (HTTP Factories) interfaces. You'll need to install a compatible implementation:
Using Guzzle (recommended):
composer require guzzlehttp/guzzleUsing Symfony HttpClient:
composer require symfony/http-client nyholm/psr7Other PSR-18/PSR-17 implementations work as well.
This module follows PSR-18 (HTTP Client) and PSR-17 (HTTP Factories) standards, providing true dependency inversion:
- No hard dependency on Guzzle - Use any PSR-compliant HTTP client
- Framework agnostic - Works with Symfony HttpClient, Guzzle, or custom clients
- Optional auto-discovery - Automatically creates Guzzle instances if available
- Full control - Inject your own configured PSR clients for advanced scenarios
This approach allows you to:
- Choose your preferred HTTP client library
- Control HTTP client configuration (timeouts, SSL, proxies, etc.)
- Test with mock PSR-18 clients
- Upgrade HTTP client versions independently
Using Docker (recommended):
docker run -d -p 8080:8080 wiremock/wiremock:latestAdd the WireMock module to your codeception.yml or suite configuration:
modules:
enabled:
- \JasonBenett\CodeceptionModuleWiremock\Module\Wiremock:
host: localhost
port: 8080<?php
class ApiTestCest
{
public function testUserEndpoint(FunctionalTester $I)
{
// Create a stub for GET /api/users/1
$I->haveHttpStubFor('GET', '/api/users/1', 200, [
'id' => 1,
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// Your application makes the HTTP request
// ... your application code ...
// Verify the request was made
$I->seeHttpRequest('GET', '/api/users/1');
}
}When using Guzzle, the module can auto-create PSR client instances:
modules:
enabled:
- \JasonBenett\CodeceptionModuleWiremock\Module\Wiremock:
host: 127.0.0.1 # WireMock server host
port: 8080 # WireMock server port
protocol: http # Protocol (http or https)
cleanupBefore: test # When to cleanup: 'never', 'test', or 'suite'
preserveFileMappings: true # Keep file-based stubs on reset
adminPath: /__admin # Admin API pathFor full control and dependency inversion, provide your own PSR-18/PSR-17 implementations:
// tests/_bootstrap.php or tests/_support/Helper/HttpClientProvider.php
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
// Create PSR-18 HTTP Client
$httpClient = new Client([
'timeout' => 10.0,
'verify' => false, // Disable SSL verification if needed
'http_errors' => false,
]);
// Create PSR-17 factories (Guzzle's HttpFactory implements both interfaces)
$httpFactory = new HttpFactory();
// Store in global for Codeception access
$GLOBALS['wiremock_http_client'] = $httpClient;
$GLOBALS['wiremock_request_factory'] = $httpFactory;
$GLOBALS['wiremock_stream_factory'] = $httpFactory;# codeception.yml or suite config
modules:
enabled:
- \JasonBenett\CodeceptionModuleWiremock\Module\Wiremock:
host: 127.0.0.1
port: 8080
httpClient: !php/const GLOBALS['wiremock_http_client']
requestFactory: !php/const GLOBALS['wiremock_request_factory']
streamFactory: !php/const GLOBALS['wiremock_stream_factory']host: 127.0.0.1 # Required: WireMock server host
port: 8080 # Required: WireMock server port
protocol: http # Optional: Protocol (http or https)
cleanupBefore: test # Optional: When to cleanup: 'never', 'test', or 'suite'
preserveFileMappings: true # Optional: Keep file-based stubs on reset
adminPath: /__admin # Optional: Admin API path
httpClient: null # Optional: PSR-18 ClientInterface instance
requestFactory: null # Optional: PSR-17 RequestFactoryInterface instance
streamFactory: null # Optional: PSR-17 StreamFactoryInterface instanceNote: If httpClient, requestFactory, or streamFactory are not provided, the module will attempt to auto-create Guzzle instances if available.
Create an HTTP stub for any HTTP method with advanced request matching.
$stubId = $I->haveHttpStubFor(
string $method, // HTTP method (GET, POST, PUT, DELETE, etc.)
string $url, // URL or URL pattern
int $status = 200, // HTTP status code
$body = '', // Response body
array $headers = [], // Response headers
array $requestMatchers = [] // Additional request matching criteria
): string; // Returns stub UUIDExamples:
// Simple POST stub
$I->haveHttpStubFor('POST', '/api/users', 201, ['success' => true]);
// POST with body pattern matching
$I->haveHttpStubFor('POST', '/api/users', 201, ['created' => true], [], [
'bodyPatterns' => [
['equalToJson' => ['name' => 'Jane Doe', 'email' => 'jane@example.com']]
]
]);
// PUT with header matching
$I->haveHttpStubFor('PUT', '/api/users/1', 200, ['updated' => true], [], [
'headers' => [
'Authorization' => [
'matches' => 'Bearer .*'
]
]
]);
// DELETE with query parameters
$I->haveHttpStubFor('DELETE', '/api/users', 204, '', [], [
'queryParameters' => [
'id' => ['equalTo' => '123']
]
]);Verify that an HTTP request was made.
$I->seeHttpRequest(
string $method, // HTTP method
string $url, // URL or URL pattern
array $additionalMatchers = [] // Additional matching criteria
): void;Examples:
// Basic verification
$I->seeHttpRequest('GET', '/api/users');
// With body verification
$I->seeHttpRequest('POST', '/api/users', [
'bodyPatterns' => [
['contains' => 'john@example.com']
]
]);
// With header verification
$I->seeHttpRequest('GET', '/api/data', [
'headers' => [
'Authorization' => ['matches' => 'Bearer .*']
]
]);Verify that an HTTP request was NOT made.
$I->dontSeeHttpRequest(
string $method, // HTTP method
string $url, // URL or URL pattern
array $additionalMatchers = [] // Additional matching criteria
): void;Example:
// Verify endpoint was not called
$I->dontSeeHttpRequest('DELETE', '/api/users/1');Assert exact number of requests matching criteria.
$I->seeRequestCount(
int $expectedCount, // Expected number of requests
array $requestPattern // Request matching pattern
): void;Examples:
// Verify exactly 3 requests
$I->seeRequestCount(3, ['method' => 'GET', 'url' => '/api/data']);
// Verify no requests to endpoint
$I->seeRequestCount(0, ['method' => 'DELETE', 'url' => '/api/users']);Get count of requests matching criteria.
$count = $I->grabRequestCount(
array $requestPattern // Request matching pattern
): int;Example:
$count = $I->grabRequestCount(['method' => 'POST', 'url' => '/api/users']);
codecept_debug("Received {$count} POST requests");Retrieve all recorded requests.
$requests = $I->grabAllRequests(): array;Example:
$requests = $I->grabAllRequests();
foreach ($requests as $request) {
codecept_debug($request['method'] . ' ' . $request['url']);
}Get requests that didn't match any stub (returned 404).
$unmatched = $I->grabUnmatchedRequests(): array;Example:
$unmatched = $I->grabUnmatchedRequests();
if (!empty($unmatched)) {
codecept_debug('Unmatched requests:', $unmatched);
}Reset WireMock to default state (preserves file-based stubs if configured).
$I->sendReset(): void;Clear the request journal without affecting stub mappings.
$I->sendClearRequests(): void;WireMock supports powerful request matching. Here are common patterns:
// Exact match
['url' => '/exact/path']
// Regex pattern
['urlPattern' => '/api/users/.*']
// Path only (ignores query params)
['urlPath' => '/api/users']
// Path with regex
['urlPathPattern' => '/api/.*/items']'bodyPatterns' => [
['equalTo' => 'exact string'],
['contains' => 'substring'],
['matches' => 'regex.*pattern'],
['equalToJson' => ['key' => 'value']],
['matchesJsonPath' => '$.store.book[?(@.price < 10)]'],
['equalToXml' => '<root>...</root>']
]'headers' => [
'Content-Type' => ['equalTo' => 'application/json'],
'Authorization' => ['matches' => 'Bearer .*'],
'X-Custom' => ['contains' => 'value']
]<?php
class ShoppingCartCest
{
public function testAddItemToCart(FunctionalTester $I)
{
// Setup: Create stub for adding item
$I->haveHttpStubFor('POST', '/api/cart/items', 201,
['id' => 123, 'quantity' => 1],
['Content-Type' => 'application/json'],
[
'bodyPatterns' => [
['matchesJsonPath' => '$.productId'],
['matchesJsonPath' => '$.quantity']
]
]
);
// Setup: Create stub for getting cart
$I->haveHttpStubFor('GET', '/api/cart', 200, [
'items' => [
['id' => 123, 'productId' => 'PROD-1', 'quantity' => 1]
],
'total' => 29.99
]);
// Act: Your application code that interacts with the API
// $cartService->addItem('PROD-1', 1);
// $cart = $cartService->getCart();
// Assert: Verify the expected requests were made
$I->seeHttpRequest('POST', '/api/cart/items', [
'bodyPatterns' => [
['matchesJsonPath' => '$.productId']
]
]);
$I->seeHttpRequest('GET', '/api/cart');
// Verify request count
$I->seeRequestCount(1, ['method' => 'POST', 'url' => '/api/cart/items']);
// Debug: Check all requests if needed
$allRequests = $I->grabAllRequests();
codecept_debug('Total requests made:', count($allRequests));
}
public function testEmptyCart(FunctionalTester $I)
{
// Verify no cart operations were performed
$I->dontSeeHttpRequest('POST', '/api/cart/items');
$I->dontSeeHttpRequest('GET', '/api/cart');
}
}The project includes a docker-compose.yml for easy local development:
# Start WireMock
docker-compose up -d
# Check status
docker-compose ps
# View logs
docker-compose logs -f wiremock
# Stop WireMock
docker-compose down# Install dependencies
composer install
# Run unit tests (don't require WireMock)
composer test
# Start WireMock
docker-compose up -d
# Build Codeception support classes
vendor/bin/codecept build
# Run functional tests (require WireMock)
composer test:functionalWhen a verification fails, the module automatically includes near-miss analysis in the error message:
Expected request not found: GET /api/users
Near misses found:
1. GET /api/user
Distance: 0.1
2. GET /api/users/1
Distance: 0.2
$unmatched = $I->grabUnmatchedRequests();
if (!empty($unmatched)) {
codecept_debug('These requests did not match any stub:');
foreach ($unmatched as $request) {
codecept_debug($request['method'] . ' ' . $request['url']);
}
}$allRequests = $I->grabAllRequests();
codecept_debug('All requests made:', $allRequests);This project maintains high code quality standards with comprehensive automated checks:
Every push and pull request is automatically tested via GitHub Actions across multiple PHP versions:
- ✅ PHP 8.2, 8.3, 8.4 - Full compatibility testing
- ✅ PHPStan Level Max - Zero errors in static analysis
- ✅ PER Coding Style 3.0 - Strict code style compliance
- ✅ Testing - Unit and Functional
- ✅ WireMock Integration Tests - Tests against real WireMock server
Run all quality checks before submitting:
# Static analysis
composer phpstan
# Code style check
composer cs-check
# Fix code style issues
composer cs-fix
# Run tests
composer test
composer test:functional- PHPStan: Max level, zero errors
- Code Coverage: with Codecov reporting
- Code Style: PER Coding Style 3.0 (successor to PSR-12)
- Type Safety: Full PHPDoc annotations with array shapes
- Documentation: Comprehensive inline documentation
Contributions are welcome! Please see CONTRIBUTING.md for detailed guidelines including:
- Development workflow and setup
- Code quality standards
- Testing requirements
- Commit message conventions
- Pull request process
MIT