Skip to content

Commit b8f45a4

Browse files
authored
Merge pull request #6 from DEVizzent/I-5_WasCalled_error_as_diff
I 5 Show wasCalled error as diff with the most similar not matched request
2 parents e351dd0 + 7ab7ed4 commit b8f45a4

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ down: ## Stop application containers and required services
2323
@docker-compose down
2424

2525
test: ## Execute all phpunit test
26-
@docker-compose exec ${PHP_CONTAINER_NAME} ./vendor/bin/phpunit
26+
@docker-compose exec ${PHP_CONTAINER_NAME} ./vendor/bin/phpunit ${TEST}
2727

2828
code-sniff cs: ## Detect coding standard violations in all project files using code sniffer
2929
@docker-compose exec ${PHP_CONTAINER_NAME} ./vendor/bin/phpcs

Diff for: composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"php": "7.4.*||8.*",
2626
"guzzlehttp/guzzle": "^7.0",
2727
"codeception/codeception": "4.*||5.*",
28-
"phpunit/phpunit": "^8.5 || 9.*"
28+
"phpunit/phpunit": "^8.5 || 9.*",
29+
"jfcherng/php-diff": "^6.14"
2930
},
3031
"require-dev": {
3132
"phpmd/phpmd": "2.*",

Diff for: src/Client/MockServer.php

+31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace DEVizzent\CodeceptionMockServerHelper\Client;
44

5+
use DEVizzent\CodeceptionMockServerHelper\MockServerHelper;
56
use GuzzleHttp\Client;
67
use GuzzleHttp\Psr7\Request;
78
use PHPUnit\Framework\Assert;
@@ -33,6 +34,34 @@ public function verify(string $expectationId, ?int $times = null): void
3334
$response->getBody()->getContents()
3435
);
3536
}
37+
38+
/**
39+
* @return array<int, array{
40+
* method: string,
41+
* path: string,
42+
* headers:array<string, string>,
43+
* keepAlive: bool,
44+
* secure: bool,
45+
* protocol: string,
46+
* body?: string
47+
* }>
48+
* @throws \Psr\Http\Client\ClientExceptionInterface
49+
*/
50+
public function getNotMatchedRequests(): array
51+
{
52+
$notMatchedRequests = [];
53+
$request = new Request('PUT', '/mockserver/retrieve?format=json&type=request_responses');
54+
$response = $this->mockserverClient->sendRequest($request);
55+
$requestResponses = json_decode($response->getBody()->getContents(), true);
56+
foreach ($requestResponses as $requestResponse) {
57+
$message = $requestResponse['httpResponse']['body']['message'] ?? '';
58+
if ($message === 'Request not matched by MockServer') {
59+
$notMatchedRequests[] = $requestResponse['httpRequest'];
60+
}
61+
}
62+
return $notMatchedRequests;
63+
}
64+
3665
public function create(string $json): void
3766
{
3867
$request = new Request(
@@ -48,6 +77,7 @@ public function create(string $json): void
4877
$response->getBody()->getContents()
4978
);
5079
}
80+
5181
public function removeById(string $mockRequestId): void
5282
{
5383
$body = json_encode([
@@ -62,6 +92,7 @@ public function removeById(string $mockRequestId): void
6292
$response->getBody()->getContents()
6393
);
6494
}
95+
6596
public function removeAllExpectations(): void
6697
{
6798
$request = new Request('PUT', '/mockserver/clear?type=expectations');

Diff for: src/MockServerHelper.php

+35-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use DEVizzent\CodeceptionMockServerHelper\Config\ExpectationsPath;
1111
use DEVizzent\CodeceptionMockServerHelper\Config\NotMatchedRequest;
1212
use GuzzleHttp\Client;
13+
use Jfcherng\Diff\DiffHelper;
1314
use PHPUnit\Framework\Assert;
1415
use PHPUnit\Framework\AssertionFailedError;
1516
use PHPUnit\Framework\ExpectationFailedException;
@@ -25,6 +26,7 @@ class MockServerHelper extends Module
2526
private CleanUpBefore $cleanUpBefore;
2627
private NotMatchedRequest $notMatchedRequest;
2728
private ExpectationsPath $expectationPath;
29+
2830
/** @param array<string, string>|null $config */
2931
public function __construct(ModuleContainer $moduleContainer, ?array $config = null)
3032
{
@@ -49,7 +51,7 @@ public function _initialize(): void
4951
$this->expectationPath = new ExpectationsPath($this->config[self::CONFIG_EXPECTATIONS_PATH]);
5052
}
5153
$this->mockserver = new MockServer(new Client([
52-
'base_uri' => $this->config[self::CONFIG_URL]
54+
'base_uri' => $this->config[self::CONFIG_URL]
5355
]));
5456
if ($this->notMatchedRequest->isEnabled()) {
5557
$this->createMockRequestFromJsonFile(__DIR__ . '/not-matched-request.json');
@@ -84,7 +86,30 @@ public function _before(TestInterface $test): void
8486

8587
public function seeMockRequestWasCalled(string $expectationId, ?int $times = null): void
8688
{
87-
$this->mockserver->verify($expectationId, $times);
89+
try {
90+
$this->mockserver->verify($expectationId, $times);
91+
} catch (AssertionFailedError $exception) {
92+
//throw $exception;
93+
preg_match('#(.|\n)* expected:<(?<expected>\{(.|\n)*\})> but was:<(.|\n)*>#', $exception->getMessage(), $matches);
94+
if (!isset($matches['expected'])) {
95+
throw $exception;
96+
}
97+
$expected = json_decode($matches['expected'], true);
98+
$notMatchedRequests = $this->mockserver->getNotMatchedRequests();
99+
$currentSimilityRatio = 0;
100+
$bestDiff = '';
101+
$expectedFormatted = $this->formatMockServerRequest($expected);
102+
foreach ($notMatchedRequests as $notMatchedRequest) {
103+
$diff = DiffHelper::calculate($expectedFormatted, $this->formatMockServerRequest($notMatchedRequest));
104+
$statistics = DiffHelper::getStatistics();
105+
$similityRatio = $statistics['unmodified'] - $statistics['inserted'] - $statistics['deleted'];
106+
if ($currentSimilityRatio < $similityRatio) {
107+
$currentSimilityRatio = $similityRatio;
108+
$bestDiff = $diff;
109+
}
110+
}
111+
throw new AssertionFailedError('Impossible match request: ' . PHP_EOL . $bestDiff);
112+
}
88113
}
89114

90115
public function seeMockRequestWasNotCalled(string $expectationId): void
@@ -138,4 +163,12 @@ public function createMockRequestFromJsonFile(string $expectationFile): void
138163
Assert::assertIsString($expectationJson);
139164
$this->createMockRequest($expectationJson);
140165
}
166+
167+
/** @param array<string, mixed> $mockServerRequest */
168+
public function formatMockServerRequest(array $mockServerRequest): string
169+
{
170+
ksort($mockServerRequest);
171+
$requestFormatted = json_encode($mockServerRequest, JSON_PRETTY_PRINT);
172+
return is_string($requestFormatted) ? $requestFormatted : '';
173+
}
141174
}

Diff for: tests/Integration/SeeMockRequestWasCalledTest.php

+33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Codeception\Lib\ModuleContainer;
66
use DEVizzent\CodeceptionMockServerHelper\MockServerHelper;
77
use GuzzleHttp\Client;
8+
use PHPUnit\Framework\AssertionFailedError;
89
use PHPUnit\Framework\ExpectationFailedException;
910
use PHPUnit\Framework\TestCase;
1011

@@ -56,4 +57,36 @@ public function testExpectationNotWasCalledThrowException2(): void
5657
);
5758
$this->sot->seeMockRequestWasCalled('not-existing-expectation');
5859
}
60+
61+
public function testExpectationWasCalledButWasNotWithGoodRecomendation(): void
62+
{
63+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/posts/2', ['http_errors' => false]);
64+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/posts/2', ['http_errors' => false]);
65+
$this->client->request(
66+
'GET',
67+
'https://jsonplaceholder.typicode.com/posts/5',
68+
['http_errors' => false, 'headers' => ['randomHeader' => 'value']]
69+
);
70+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/users/3/albums', ['http_errors' => false]);
71+
72+
$this->expectException(AssertionFailedError::class);
73+
$this->expectExceptionMessageMatches('#.*"path": "\\\/users\\\/3\\\/albums".*#');
74+
$this->sot->seeMockRequestWasCalled('get-post-1');
75+
}
76+
77+
public function testExpectationWasCalledButWasNotWithGoodRecomendation2(): void
78+
{
79+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/posts/2', ['http_errors' => false]);
80+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/posts/2', ['http_errors' => false]);
81+
$this->client->request('GET', 'https://jsonplaceholder.typicode.com/posts/5', ['http_errors' => false]);
82+
$this->client->request(
83+
'GET',
84+
'https://jsonplaceholder.typicode.com/users/3/albums',
85+
['http_errors' => false, 'headers' => ['randomHeader' => 'value']]
86+
);
87+
88+
$this->expectException(AssertionFailedError::class);
89+
$this->expectExceptionMessageMatches('#.*"path": "\\\/posts\\\/5".*#');
90+
$this->sot->seeMockRequestWasCalled('get-post-1');
91+
}
5992
}

0 commit comments

Comments
 (0)