Skip to content

Commit

Permalink
[FrameworkBundle] Add HttpClientAssertionsTrait which provide short…
Browse files Browse the repository at this point in the history
…cuts to assert HTTP calls was triggered
  • Loading branch information
welcoMattic authored and fabpot committed Oct 2, 2023
1 parent 3cc427d commit 59f5eec
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
6.4
---

* Add `HttpClientAssertionsTrait`
* Add `AbstractController::renderBlock()` and `renderBlockView()`
* Add native return type to `Translator` and to `Application::reset()`
* Deprecate the integration of Doctrine annotations, either uninstall the `doctrine/annotations` package or disable the integration by setting `framework.annotations` to `false`
Expand Down
134 changes: 134 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Test/HttpClientAssertionsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Test;

use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector;

/*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/

trait HttpClientAssertionsTrait
{
public static function assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client'): void
{
/** @var KernelBrowser $client */
$client = static::getClient();

if (!($profile = $client->getProfile())) {
static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.');
}

/** @var HttpClientDataCollector $httpClientDataCollector */
$httpClientDataCollector = $profile->getCollector('http_client');
$expectedRequestHasBeenFound = false;

if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) {
static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId));
}

foreach ($httpClientDataCollector->getClients()[$httpClientId]['traces'] as $trace) {
if (($expectedUrl !== $trace['info']['url'] && $expectedUrl !== $trace['url'])
|| $expectedMethod !== $trace['method']
) {
continue;
}

if (null !== $expectedBody) {
$actualBody = null;

if (null !== $trace['options']['body'] && null === $trace['options']['json']) {
$actualBody = \is_string($trace['options']['body']) ? $trace['options']['body'] : $trace['options']['body']->getValue(true);
}

if (null === $trace['options']['body'] && null !== $trace['options']['json']) {
$actualBody = $trace['options']['json']->getValue(true);
}

if (!$actualBody) {
continue;
}

if ($expectedBody === $actualBody) {
$expectedRequestHasBeenFound = true;

if (!$expectedHeaders) {
break;
}
}
}

if ($expectedHeaders) {
$actualHeaders = $trace['options']['headers'] ?? [];

foreach ($actualHeaders as $headerKey => $actualHeader) {
if (\array_key_exists($headerKey, $expectedHeaders)
&& $expectedHeaders[$headerKey] === $actualHeader->getValue(true)
) {
$expectedRequestHasBeenFound = true;
break 2;
}
}
}

$expectedRequestHasBeenFound = true;
break;
}

self::assertTrue($expectedRequestHasBeenFound, 'The expected request has not been called: "'.$expectedMethod.'" - "'.$expectedUrl.'"');
}

public function assertNotHttpClientRequest(string $unexpectedUrl, string $expectedMethod = 'GET', string $httpClientId = 'http_client'): void
{
/** @var KernelBrowser $client */
$client = static::getClient();

if (!$profile = $client->getProfile()) {
static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.');
}

/** @var HttpClientDataCollector $httpClientDataCollector */
$httpClientDataCollector = $profile->getCollector('http_client');
$unexpectedUrlHasBeenFound = false;

if (!\array_key_exists($httpClientId, $httpClientDataCollector->getClients())) {
static::fail(sprintf('HttpClient "%s" is not registered.', $httpClientId));
}

foreach ($httpClientDataCollector->getClients()[$httpClientId]['traces'] as $trace) {
if (($unexpectedUrl === $trace['info']['url'] || $unexpectedUrl === $trace['url'])
&& $expectedMethod === $trace['method']
) {
$unexpectedUrlHasBeenFound = true;
break;
}
}

self::assertFalse($unexpectedUrlHasBeenFound, sprintf('Unexpected URL called: "%s" - "%s"', $expectedMethod, $unexpectedUrl));
}

public static function assertHttpClientRequestCount(int $count, string $httpClientId = 'http_client'): void
{
/** @var KernelBrowser $client */
$client = static::getClient();

if (!($profile = $client->getProfile())) {
static::fail('The Profiler must be enabled for the current request. Please ensure to call "$client->enableProfiler()" before making the request.');
}

/** @var HttpClientDataCollector $httpClientDataCollector */
$httpClientDataCollector = $profile->getCollector('http_client');

self::assertCount($count, $httpClientDataCollector->getClients()[$httpClientId]['traces']);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ trait WebTestAssertionsTrait
{
use BrowserKitAssertionsTrait;
use DomCrawlerAssertionsTrait;
use HttpClientAssertionsTrait;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class HttpClientController
{
public function index(HttpClientInterface $httpClient, HttpClientInterface $symfonyHttpClient): Response
{
$httpClient->request('GET', 'https://symfony.com/');

$symfonyHttpClient->request('GET', '/');
$symfonyHttpClient->request('POST', '/', ['body' => 'foo']);
$symfonyHttpClient->request('POST', '/', ['body' => ['foo' => 'bar']]);
$symfonyHttpClient->request('POST', '/', ['json' => ['foo' => 'bar']]);
$symfonyHttpClient->request('POST', '/', [
'headers' => ['X-Test-Header' => 'foo'],
'json' => ['foo' => 'bar'],
]);
$symfonyHttpClient->request('GET', '/doc/current/index.html');

return new Response();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ send_email:
path: /send_email
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\EmailController::indexAction }

http_client_call:
path: /http_client_call
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\HttpClientController::index }

uid:
resource: "../../Controller/UidController.php"
type: "annotation"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Tests;

use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\ResponseInterface;

class MockClientCallback
{
public function __invoke(string $method, string $url, array $options = []): ResponseInterface
{
return new MockResponse('foo');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;

class HttpClientTest extends AbstractWebTestCase
{
public function testHttpClientAssertions()
{
$client = $this->createClient(['test_case' => 'HttpClient', 'root_config' => 'config.yml', 'debug' => true]);
$client->enableProfiler();
$client->request('GET', '/http_client_call');

$this->assertHttpClientRequest('https://symfony.com/');
$this->assertHttpClientRequest('https://symfony.com/', httpClientId: 'symfony.http_client');
$this->assertHttpClientRequest('https://symfony.com/', 'POST', 'foo', httpClientId: 'symfony.http_client');
$this->assertHttpClientRequest('https://symfony.com/', 'POST', ['foo' => 'bar'], httpClientId: 'symfony.http_client');
$this->assertHttpClientRequest('https://symfony.com/', 'POST', ['foo' => 'bar'], httpClientId: 'symfony.http_client');
$this->assertHttpClientRequest('https://symfony.com/', 'POST', ['foo' => 'bar'], ['X-Test-Header' => 'foo'], 'symfony.http_client');
$this->assertHttpClientRequest('https://symfony.com/doc/current/index.html', httpClientId: 'symfony.http_client');
$this->assertNotHttpClientRequest('https://laravel.com', httpClientId: 'symfony.http_client');

$this->assertHttpClientRequestCount(6, 'symfony.http_client');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;

return [
new FrameworkBundle(),
new TestBundle(),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
imports:
- { resource: ../config/default.yml }
- { resource: services.yml }

framework:
http_method_override: false
profiler: ~
http_client:
mock_response_factory: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Tests\MockClientCallback
scoped_clients:
symfony.http_client:
base_uri: 'https://symfony.com'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_emailtest_bundle:
resource: '@TestBundle/Resources/config/routing.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
public: true

Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\HttpClientController:
tags: ['controller.service_arguments']

Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Tests\MockClientCallback:
class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Tests\MockClientCallback

0 comments on commit 59f5eec

Please sign in to comment.