Skip to content

Commit 4c4a77e

Browse files
JasonBenettclaude
andcommitted
fix: use urlPath for query parameter matching in WireMock requests
When queryParameters are specified in request matchers, WireMock requires 'urlPath' instead of 'url' to match the path separately from query params. This fix: - Adds determineUrlKey() method to automatically select correct URL matcher - Eliminates code duplication across haveHttpStubFor, seeHttpRequest, dontSeeHttpRequest - Respects explicit user overrides for urlPath/urlPattern - Adds comprehensive unit tests with data provider using generators - Updates documentation to replace docker-compose with docker run commands 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 4c2ec0e commit 4c4a77e

File tree

5 files changed

+132
-20
lines changed

5 files changed

+132
-20
lines changed

CLAUDE.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ composer cs-fix # Fix code style issues
6262

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

7071
## Development Workflow & Conventions
@@ -249,13 +250,13 @@ modules:
249250

250251
1. Start WireMock server:
251252
```bash
252-
docker-compose up -d
253+
docker run -d -p 8080:8080 wiremock/wiremock:latest
253254
```
254255

255-
2. Wait for WireMock to be healthy:
256+
2. Wait for WireMock to be ready:
256257
```bash
257-
docker-compose ps
258-
# Should show "healthy" status
258+
curl http://localhost:8080/__admin/health
259+
# Should return: {"status":"OK"}
259260
```
260261

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

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

283284
## Common Patterns

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ composer install
5353
### Start WireMock Server
5454

5555
```bash
56-
docker-compose up -d
56+
docker run -d -p 8080:8080 wiremock/wiremock:latest
5757
```
5858

5959
Verify WireMock is running:
@@ -93,7 +93,7 @@ composer test
9393
Functional tests require WireMock to be running:
9494

9595
```bash
96-
docker-compose up -d
96+
docker run -d -p 8080:8080 wiremock/wiremock:latest
9797
composer test:functional
9898
```
9999

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -473,22 +473,22 @@ class ShoppingCartCest
473473

474474
## Local Development
475475

476-
### Using Docker Compose
476+
### Using Docker
477477

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

480480
```bash
481481
# Start WireMock
482-
docker-compose up -d
482+
docker run -d -p 8080:8080 wiremock/wiremock:latest
483483
484484
# Check status
485-
docker-compose ps
485+
docker ps | grep wiremock
486486
487487
# View logs
488-
docker-compose logs -f wiremock
488+
docker logs <container-id>
489489
490490
# Stop WireMock
491-
docker-compose down
491+
docker stop <container-id>
492492
```
493493

494494
### Running Tests
@@ -501,7 +501,7 @@ composer install
501501
composer test
502502
503503
# Start WireMock
504-
docker-compose up -d
504+
docker run -d -p 8080:8080 wiremock/wiremock:latest
505505
506506
# Build Codeception support classes
507507
vendor/bin/codecept build

src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,12 @@ public function haveHttpStubFor(
138138
array $headers = [],
139139
array $requestMatchers = [],
140140
): string {
141+
$urlKey = $this->determineUrlKey($requestMatchers);
142+
141143
$mapping = [
142144
'request' => array_merge([
143145
'method' => strtoupper($method),
144-
'url' => $url,
146+
$urlKey => $url,
145147
], $requestMatchers),
146148
'response' => [
147149
'status' => $status,
@@ -187,9 +189,11 @@ public function seeHttpRequest(
187189
string $url,
188190
array $additionalMatchers = [],
189191
): void {
192+
$urlKey = $this->determineUrlKey($additionalMatchers);
193+
190194
$pattern = array_merge([
191195
'method' => strtoupper($method),
192-
'url' => $url,
196+
$urlKey => $url,
193197
], $additionalMatchers);
194198

195199
$count = $this->grabRequestCount($pattern);
@@ -227,9 +231,11 @@ public function dontSeeHttpRequest(
227231
string $url,
228232
array $additionalMatchers = [],
229233
): void {
234+
$urlKey = $this->determineUrlKey($additionalMatchers);
235+
230236
$pattern = array_merge([
231237
'method' => strtoupper($method),
232-
'url' => $url,
238+
$urlKey => $url,
233239
], $additionalMatchers);
234240

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

609615
$this->streamFactory = $streamFactory;
610616
}
617+
618+
/**
619+
* Determine whether to use 'url' or 'urlPath' based on request matchers
620+
*
621+
* When queryParameters are specified in request matchers, WireMock requires
622+
* 'urlPath' instead of 'url' to match the path separately from query params.
623+
*
624+
* @param array<string, mixed> $matchers Request matcher array
625+
*
626+
* @return string Either 'url' or 'urlPath'
627+
*/
628+
private function determineUrlKey(array $matchers): string
629+
{
630+
// If queryParameters are present and user hasn't explicitly set urlPath/urlPattern,
631+
// use 'urlPath' for path-only matching (allowing separate query param matching)
632+
if (
633+
isset($matchers['queryParameters'])
634+
&& !isset($matchers['urlPath'])
635+
&& !isset($matchers['urlPattern'])
636+
) {
637+
return 'urlPath';
638+
}
639+
640+
// Default to 'url' for exact URL matching
641+
return 'url';
642+
}
611643
}

tests/unit/Codeception/Module/WiremockTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Codeception\Lib\ModuleContainer;
88
use Codeception\Test\Unit;
9+
use Generator;
910
use GuzzleHttp\Client;
1011
use GuzzleHttp\Handler\MockHandler;
1112
use GuzzleHttp\HandlerStack;
@@ -270,4 +271,82 @@ public function testSendClearRequestsCallsClearEndpoint(): void
270271
// Should not throw exception
271272
$this->assertTrue(true);
272273
}
274+
275+
/**
276+
* Test determineUrlKey method for proper url vs urlPath selection
277+
*
278+
* @dataProvider urlKeyDataProvider
279+
*/
280+
public function testDetermineUrlKey(array $matchers, string $expectedKey): void
281+
{
282+
$reflection = new ReflectionClass($this->module);
283+
$method = $reflection->getMethod('determineUrlKey');
284+
285+
$result = $method->invoke($this->module, $matchers);
286+
287+
$this->assertSame($expectedKey, $result);
288+
}
289+
290+
/**
291+
* Data provider for testDetermineUrlKey
292+
*
293+
* @return Generator<string, array{matchers: array<string, mixed>, expectedKey: string}>
294+
*/
295+
public static function urlKeyDataProvider(): Generator
296+
{
297+
yield 'no matchers - uses url' => [
298+
'matchers' => [],
299+
'expectedKey' => 'url',
300+
];
301+
302+
yield 'only method matcher - uses url' => [
303+
'matchers' => ['method' => 'GET'],
304+
'expectedKey' => 'url',
305+
];
306+
307+
yield 'with queryParameters - uses urlPath' => [
308+
'matchers' => [
309+
'queryParameters' => ['q' => ['equalTo' => 'London']],
310+
],
311+
'expectedKey' => 'urlPath',
312+
];
313+
314+
yield 'with queryParameters and other matchers - uses urlPath' => [
315+
'matchers' => [
316+
'queryParameters' => ['q' => ['equalTo' => 'London']],
317+
'headers' => ['Content-Type' => ['equalTo' => 'application/json']],
318+
],
319+
'expectedKey' => 'urlPath',
320+
];
321+
322+
yield 'explicit urlPath overrides - uses url (respects user choice)' => [
323+
'matchers' => [
324+
'queryParameters' => ['q' => ['equalTo' => 'London']],
325+
'urlPath' => '/api/weather',
326+
],
327+
'expectedKey' => 'url',
328+
];
329+
330+
yield 'explicit urlPattern overrides - uses url (respects user choice)' => [
331+
'matchers' => [
332+
'queryParameters' => ['q' => ['equalTo' => 'London']],
333+
'urlPattern' => '/api/.*',
334+
],
335+
'expectedKey' => 'url',
336+
];
337+
338+
yield 'only bodyPatterns - uses url' => [
339+
'matchers' => [
340+
'bodyPatterns' => [['equalToJson' => '{"name":"test"}']],
341+
],
342+
'expectedKey' => 'url',
343+
];
344+
345+
yield 'only headers - uses url' => [
346+
'matchers' => [
347+
'headers' => ['Authorization' => ['matches' => 'Bearer .*']],
348+
],
349+
'expectedKey' => 'url',
350+
];
351+
}
273352
}

0 commit comments

Comments
 (0)