Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ composer cs-fix # Fix code style issues

### WireMock Server
```bash
docker-compose up -d # Start WireMock server on port 8080
docker-compose down # Stop WireMock server
docker-compose logs wiremock # View WireMock logs
docker run -d -p 8080:8080 wiremock/wiremock:latest # Start WireMock server on port 8080
docker ps | grep wiremock # Check if WireMock is running
docker logs <container-id> # View WireMock logs
docker stop <container-id> # Stop WireMock server
```

## Development Workflow & Conventions
Expand Down Expand Up @@ -249,13 +250,13 @@ modules:

1. Start WireMock server:
```bash
docker-compose up -d
docker run -d -p 8080:8080 wiremock/wiremock:latest
```

2. Wait for WireMock to be healthy:
2. Wait for WireMock to be ready:
```bash
docker-compose ps
# Should show "healthy" status
curl http://localhost:8080/__admin/health
# Should return: {"status":"OK"}
```

3. Run unit tests (don't require WireMock):
Expand All @@ -277,7 +278,7 @@ modules:

- Use `grabAllRequests()` to see all requests made
- Use `grabUnmatchedRequests()` to see requests that didn't match any stub
- Check WireMock logs: `docker-compose logs wiremock`
- Check WireMock logs: `docker logs <container-id>`
- Near-miss analysis is automatically included in verification error messages

## Common Patterns
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ composer install
### Start WireMock Server

```bash
docker-compose up -d
docker run -d -p 8080:8080 wiremock/wiremock:latest
```

Verify WireMock is running:
Expand Down Expand Up @@ -93,7 +93,7 @@ composer test
Functional tests require WireMock to be running:

```bash
docker-compose up -d
docker run -d -p 8080:8080 wiremock/wiremock:latest
composer test:functional
```

Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,22 +473,22 @@ class ShoppingCartCest

## Local Development

### Using Docker Compose
### Using Docker

The project includes a `docker-compose.yml` for easy local development:
You can run WireMock using Docker:

```bash
# Start WireMock
docker-compose up -d
docker run -d -p 8080:8080 wiremock/wiremock:latest

# Check status
docker-compose ps
docker ps | grep wiremock

# View logs
docker-compose logs -f wiremock
docker logs <container-id>

# Stop WireMock
docker-compose down
docker stop <container-id>
```

### Running Tests
Expand All @@ -501,7 +501,7 @@ composer install
composer test

# Start WireMock
docker-compose up -d
docker run -d -p 8080:8080 wiremock/wiremock:latest

# Build Codeception support classes
vendor/bin/codecept build
Expand Down
38 changes: 35 additions & 3 deletions src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,12 @@ public function haveHttpStubFor(
array $headers = [],
array $requestMatchers = [],
): string {
$urlKey = $this->determineUrlKey($requestMatchers);

$mapping = [
'request' => array_merge([
'method' => strtoupper($method),
'url' => $url,
$urlKey => $url,
], $requestMatchers),
'response' => [
'status' => $status,
Expand Down Expand Up @@ -187,9 +189,11 @@ public function seeHttpRequest(
string $url,
array $additionalMatchers = [],
): void {
$urlKey = $this->determineUrlKey($additionalMatchers);

$pattern = array_merge([
'method' => strtoupper($method),
'url' => $url,
$urlKey => $url,
], $additionalMatchers);

$count = $this->grabRequestCount($pattern);
Expand Down Expand Up @@ -227,9 +231,11 @@ public function dontSeeHttpRequest(
string $url,
array $additionalMatchers = [],
): void {
$urlKey = $this->determineUrlKey($additionalMatchers);

$pattern = array_merge([
'method' => strtoupper($method),
'url' => $url,
$urlKey => $url,
], $additionalMatchers);

$count = $this->grabRequestCount($pattern);
Expand Down Expand Up @@ -608,4 +614,30 @@ private function initStreamFactory(): void

$this->streamFactory = $streamFactory;
}

/**
* Determine whether to use 'url' or 'urlPath' based on request matchers
*
* When queryParameters are specified in request matchers, WireMock requires
* 'urlPath' instead of 'url' to match the path separately from query params.
*
* @param array<string, mixed> $matchers Request matcher array
*
* @return string Either 'url' or 'urlPath'
*/
private function determineUrlKey(array $matchers): string
{
// If queryParameters are present and user hasn't explicitly set urlPath/urlPattern,
// use 'urlPath' for path-only matching (allowing separate query param matching)
if (
isset($matchers['queryParameters'])
&& !isset($matchers['urlPath'])
&& !isset($matchers['urlPattern'])
) {
return 'urlPath';
}

// Default to 'url' for exact URL matching
return 'url';
}
}
79 changes: 79 additions & 0 deletions tests/unit/Codeception/Module/WiremockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Codeception\Lib\ModuleContainer;
use Codeception\Test\Unit;
use Generator;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
Expand Down Expand Up @@ -270,4 +271,82 @@ public function testSendClearRequestsCallsClearEndpoint(): void
// Should not throw exception
$this->assertTrue(true);
}

/**
* Test determineUrlKey method for proper url vs urlPath selection
*
* @dataProvider urlKeyDataProvider
*/
public function testDetermineUrlKey(array $matchers, string $expectedKey): void
{
$reflection = new ReflectionClass($this->module);
$method = $reflection->getMethod('determineUrlKey');

$result = $method->invoke($this->module, $matchers);

$this->assertSame($expectedKey, $result);
}

/**
* Data provider for testDetermineUrlKey
*
* @return Generator<string, array{matchers: array<string, mixed>, expectedKey: string}>
*/
public static function urlKeyDataProvider(): Generator
{
yield 'no matchers - uses url' => [
'matchers' => [],
'expectedKey' => 'url',
];

yield 'only method matcher - uses url' => [
'matchers' => ['method' => 'GET'],
'expectedKey' => 'url',
];

yield 'with queryParameters - uses urlPath' => [
'matchers' => [
'queryParameters' => ['q' => ['equalTo' => 'London']],
],
'expectedKey' => 'urlPath',
];

yield 'with queryParameters and other matchers - uses urlPath' => [
'matchers' => [
'queryParameters' => ['q' => ['equalTo' => 'London']],
'headers' => ['Content-Type' => ['equalTo' => 'application/json']],
],
'expectedKey' => 'urlPath',
];

yield 'explicit urlPath overrides - uses url (respects user choice)' => [
'matchers' => [
'queryParameters' => ['q' => ['equalTo' => 'London']],
'urlPath' => '/api/weather',
],
'expectedKey' => 'url',
];

yield 'explicit urlPattern overrides - uses url (respects user choice)' => [
'matchers' => [
'queryParameters' => ['q' => ['equalTo' => 'London']],
'urlPattern' => '/api/.*',
],
'expectedKey' => 'url',
];

yield 'only bodyPatterns - uses url' => [
'matchers' => [
'bodyPatterns' => [['equalToJson' => '{"name":"test"}']],
],
'expectedKey' => 'url',
];

yield 'only headers - uses url' => [
'matchers' => [
'headers' => ['Authorization' => ['matches' => 'Bearer .*']],
],
'expectedKey' => 'url',
];
}
}