برنامه نویسی

تمسخر درخواست های API در تست های واحد

در بسیاری از برنامه ها ، ارسال درخواست به خدمات خارجی برای به دست آوردن انواع مختلف داده ها معمول است. برای اطمینان از اینکه کد ما به درستی این پاسخ ها را کنترل می کند ، آزمایش ضروری است. با این حال ، در یک محیط آزمایش ، برقراری تماس واقعی با این خدمات خارجی چیزی است که ما معمولاً می خواهیم از آن جلوگیری کنیم. Phpunit روش های مختلفی برای جلوگیری از این امر ارائه می دهد. در این پست وبلاگ ، من نشان می دهم که چگونه می توانید هنگام استفاده از مشتری GuzzleHTTP ، درخواست های HTTP را در تست های واحد خود مسخره کنید.

بیایید یک سرویس ساده “GeoCoder” را در نظر بگیریم که مختصات یک مکان را بر اساس یک آدرس معین ارائه می دهد. این GeoCoder برای برقراری ارتباط با یک سرویس خارجی ، Google Maps در این مورد ، برای بازیابی مختصات مورد نظر ، از مشتری GuzzleHTTP استفاده می کند. در اینجا نحوه ساختار کد آورده شده است:



declare(strict_types=1);

namespace App\Infrastructure\Geocoder;

use App\Domain\GeocoderInterface;
use App\Domain\ValueObject\Address;
use App\Domain\ValueObject\Coordinates;
use GuzzleHttp\Client;

final readonly class GoogleMapsGeocoder implements GeocoderInterface
{
    private const string ENDPOINT = 'https://maps.googleapis.com/maps/api/geocode/json';

    public function __construct(
        private Client $client,
        private string $apiKey
    ) {}

    public function geocode(Address $address): ?Coordinates
    {
        $params = [
            'query' => [
                'address' => $address->street,
                'components' => \sprintf(
                    'country:%s|locality:%s|postal_code:%s', 
                    $address->countryCode, 
                    $address->city, 
                    $address->postcode
                ),
                'key' => $this->apiKey,
            ],
        ];

        $response = $this->client->get(self::ENDPOINT, $params);

        $data = \json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);

        if ($data['results'] === []) {
            return null;
        }

        $firstResult = $data['results'][0];

        if ($firstResult['geometry']['location_type'] !== 'ROOFTOP') {
            return null;
        }

        return new Coordinates(
            $firstResult['geometry']['location']['lat'],
            $firstResult['geometry']['location']['lng']
        );
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

برای آزمایش سناریوهای مختلف ، مانند اینکه آیا پاسخ به درستی به یک شیء مختصات تبدیل شده است یا اینکه پاسخ خالی است ، ما باید درخواست HTTP را مسخره کنیم. ساده ترین راه برای انجام این کار با استفاده از MockHandler کلاس از GuzzleHttpبشر این امکان را به ما می دهد که یک مشتری مسخره ایجاد کنیم که یک پاسخ از پیش تعریف شده را برمی گرداند. در اینجا چگونه می توانید آن را انجام دهید:

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;

$this->handler = new MockHandler();

new Client(['handler' => new MockHandler()]);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سپس می توانیم پاسخی به کنترل کننده اضافه کنیم:

$this->handler->append(
    new Response(200, [], \json_encode([
        'results' => [
            [
                'geometry' => [
                    'location' => [
                        'lat' => '1.0',
                        'lng' => '2.0',
                    ],
                    'location_type' => 'ROOFTOP',
                ],
            ],
        ],
    ]))
);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بعد از تماس با سرویس خارجی ، می توانیم با استفاده از پارامترهای صحیح ، درخواست را بررسی کنیم getLastRequest() روش:

$this->handler->getLastRequest();
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سرانجام ، کل تست واحد می تواند به این شکل باشد:



declare(strict_types=1);

namespace App\Tests\Infrastructure\Geocoder;

use App\Domain\ValueObject\Address;
use App\Infrastructure\Geocoder\GoogleMapsGeocoder;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;

final class GoogleMapsGeocoderUnitTest extends TestCase
{
    private const string API_KEY = 'api-key';

    private readonly MockHandler $handler;

    private readonly GoogleMapsGeocoder $fixture;

    protected function setUp(): void
    {
        parent::setUp();

        $this->handler = new MockHandler();

        $client = new Client(['handler' => $this->handler]);

        $this->fixture = new GoogleMapsGeocoder(
            $client,
            self::API_KEY,
        );
    }

    #[Test]
    public function it_geocodes_address(): void
    {
        $address = new Address(
            countryCode: 'UK',
            city: 'London',
            street: 'Buckingham Palace',
            postcode: 'SW1A 1AA',
        );

        $expectedLat = '51.5073509';
        $expectedLng = '-0.1277583';

        $responseData = [
            'results' => [
                [
                    'geometry' => [
                        'location' => [
                            'lat' => $expectedLat,
                            'lng' => $expectedLng,
                        ],
                        'location_type' => 'ROOFTOP',
                    ],
                ],
            ],
        ];

        $this->handler->append(new Response(200, [], (string) \json_encode($responseData, JSON_THROW_ON_ERROR)));

        $coordinates = $this->fixture->geocode($address);
        $this->assertNotNull($coordinates);

        $this->assertSame($expectedLat, $coordinates->lat);
        $this->assertSame($expectedLng, $coordinates->lng);

        $lastRequest = $this->handler->getLastRequest();
        $this->assertNotNull($lastRequest);

        $this->assertRequest($lastRequest, $address);
    }

    private function assertRequest(RequestInterface $request, Address $address): void
    {
        $queryArray = [];
        \parse_str(
            $request->getUri()->getQuery(),
            $queryArray
        );

        $this->assertSame(self::API_KEY, $queryArray['key']);
        $this->assertSame($address->street, $queryArray['address']);
        $this->assertSame(
            \sprintf(
                'country:%s|locality:%s|postal_code:%s',
                $address->countryCode,
                $address->city,
                $address->postcode,
            ),
            $queryArray['components']
        );
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در این پست وبلاگ ، من نشان دادم که چگونه می توان درخواست های HTTP را در تست های واحد با استفاده از مشتری GuzzleHTTP مسخره کرد. با استفاده از MockHandler کلاس ، ما می توانیم یک مشتری مسخره ایجاد کنیم که پاسخ های از پیش تعریف شده را بازگرداند و به ما امکان می دهد سناریوهای مختلف را بدون برقراری تماس واقعی به خدمات خارجی آزمایش کنیم. این رویکرد برای اطمینان از رسیدگی به درست و کارآمد کد ما ضروری است.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا