PHP製のHTTP ClientのGuzzleでユニットテストを書いていく方法

PHP製のHTTP Clientライブラリの「Guzzle」を使っていると、ユニットテストをしたくなってくる。

幸いにも、Guzzleはテストを行いやすい内部の仕組みができている。 具体的には、handlerを差し込むことによって、テストが可能になるので、その方法を見ていく。

Guzzleのテストの方法

先ほど説明したように、Guzzleではhandlerを設定することによって、Guzzleのリクエスト時の動作を設定することができる。(デフォルトでは、Curlが動く様になっている)

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Exception\RequestException;

// Create a mock and queue two responses.
$mock = new MockHandler([
    new Response(200, ['X-Foo' => 'Bar'], 'Hello, World'),
    new Response(202, ['Content-Length' => 0]),
    new RequestException('Error Communicating with Server', new Request('GET', 'test'))
]);

$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);

使い方は上記のようにMockHandlerを使ってGuzzleのResponseの配列を指定した後に、HandlerStack::createでhandlerを作成する。

上記の例で$client->request("GET", "/sample")等とリクエストを送ると、1回目は new Response(200, ['X-Foo' => 'Bar'], 'Hello, World')がレスポンスとして返されて、2回目は new Response(202, ['Content-Length' => 0])が返される。

そして最後に、RequestExceptionが返されるように設定できる。

参考記事: Testing Guzzle Clients — Guzzle Documentation

実際のwebアプリでのテストの方法

では、実際のwebアプリではどの様にテストを行なっていくか。 ここでは具体例として、Twitterのクライアントwebアプリを取り扱う。

この手のアプリでは、以下のようなクラスが用意されているはずだ。

<?php

use GuzzleHttp\Client;

class TwitterApiClient 
{
    public function requestUser(string $twitterUserName): object
    {
        $client = new Client();
        $res = $client->request("GET", "/users/get/{$twitterUserName}", [
            "headers" => []
        ]);
        return json_encode($res->getBody());
    }
}

コードはかなり適当だが、このようなコードはたくさんある。

ここでテストのために上記のコードに以下のstaticメソッドを追加し、requestUserメソッドを少し変更する。

<?php

use GuzzleHttp\Client;

class TwitterApiClient 
{
    protected static $handler;

    public static function mockGuzzleHandler($handler)
    {
        self::$handler = $handler;
    }

    public function requestUser(string $twitterUserName): object
    {
        $client = new Client();

        $options = [
            "headers" => []
        ];

        if(isset(self::$handler)) $options["handler"] = self::$handler;

        $res = $client->request("GET", "/users/get/{$twitterUserName}", $options);
        return json_encode($res->getBody());
    }
}

そしてテストコードを書くときには、以下のようにhandlerを差し込んでやる。

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use TestCase;

class SampleTest extends TestCase
{
    public function testGuzzle()
    {
        $mock = new MockHandler([
            // /data/get_user.jsonにtwitterAPIのレスポンスと似たようなjsonデータを用意しておく。
            new Response(200, [], file_get_contents(__DIR__ . "/data/get_user.json")),
        ]);

        $handler = HandlerStack::create($mock);

        TwitterApiClient::mockGuzzleHandler($handler);

        // テストを実行
        $this->get(route("twitter.index"));

        // 以下は結果の検証を行う。
    }
}

これでGuzzleのテストができる。他にもやり方は色々あると思うが、この方法だとGuzzleのリクエスト部分だけ Mockを差し込むことになるので、リクエストの関数全体をMockでするよりも良いテストになるだろう。