phpunit で Laravel middleware の ajax request をテストする

phpunit で ajax リクエストかどうか判定したい.
結構ハマったのでメモ.

概要は次の通り.

動作環境

  • php 7.2.x
  • Laravel 5.5.x
  • phpunit 6.5.x

前提条件 & 事前知識

前提条件

  • phpunit の基本的な使い方は先日まとめたこちらの記事を参照.
  • Laravel のバージョンによって project dir. 構造が多少異なるので注意.

事前知識

いくつか知っておいた方がいい知識を紹介.

  • Laravel の Request class は Symfony\Component\HttpFoundation\Request を拡張している
  • ajax 判定は Laravel ヘルパの $request->ajax() で可能
  • unit テスト内では middleware をインスタンス化し, handle() の引数に任意の closure を渡すことが可能

Laravel の Request class は Symfony\Component\HttpFoundation\Request を拡張している

Laravel の公式ドキュメントには次のように書かれている.

lluminate\Http\Requestインスタンスは、Symfony\Component\HttpFoundation\Requestクラスを拡張しており

引用: https://readouble.com/laravel/5.5/ja/requests.html

今回作成するテストでは, 親クラスのメソッドを利用する.
必要に応じて Symfony\Component\HttpFoundation\Request のソースを眺めておくといいかも.

  • https://github.com/symfony/symfony/blob/2.7/src/Symfony/Component/HttpFoundation/Request.php

ajax 判定は Laravel ヘルパの $request->ajax() で可能

これは Laravel API ドキュメントに載っている.

bool
ajax()
Determine if the request is the result of an AJAX call.

引用: https://laravel.com/api/4.2/Illuminate/Http/Request.html

ajax リクエストの動作確認には postman 等の HTTP Client Tool がよく用いられる.

  • https://www.getpostman.com/

こういうツールは便利なんだけど, 設定周りでハマる事が多い. ログイン認証したいときとか.

今回はコマンドラインから curl を実行して動作確認を行う.
ヘッダを編集することで ajax からのリクエストのような振る舞いが可能.
たとえばこんな感じで↓

$ curl -H "X-Requested-With: XMLHttpRequest" リクエスト先

middleware 内で $request->ajax() が期待通りの動作をするかちょろっと確認したいならこれで十分.

unit テスト内では middleware をインスタンス化し, handle() の引数に任意の closure を渡すことが可能

ぱっと middleware の method を眺めると handle() がある.
phpunit 内で new でインスタンス化しらた, 任意の closure を handle() の引数に渡そう.
今回は対象の middleware を通過した合図として, true を返す closure を設定した.
この点については後述のコードを参照.

今回やりたいこと

phpunit でサイトへのアクセスが ajax によるものか, それ以外かを判定するテストがしたい.

事前準備

ひとまず route へのアクセス時に通過する before middleware を作っておく.

<php

// app/Http/Middleware/SampleBeforeMiddleware.php

namespace App\Http\Middleware;

use Closure;

class SampleBeforeMiddleware
{
    public function handle($request, Closure $next)
    {
        if ($request->ajax()) {
            return redirect('/', 301); // ajax リクエストだったら 301 リダイレクトする
        }

        return $next($request);
    }
}

↑コレ は ↓こんな middleware

  • ajax リクエストを受けたら 301 redirect する middlewar

middleware を作ったら Kernel.php に登録するのを忘れずに.

試しに curl で ajax リクエストをしてみる.
もし期待通りの動作なら, response header に 301 redirect の表示があるはず.

curl で response header 1行目 を取得してみる.

$ curl -D - -o /dev/null -skH "X-Requested-With: XMLHttpRequest" リクエスト先 | head -1
HTTP/1.1 301 Moved Permanently

curl オプションを次のようにすることで擬似的に ajax リクエストの生成が可能.
期待通りにリダイレクトされていることが判る.

  • -H "X-Requested-With: XMLHttpRequest"

ここからは phpunit の準備をする.

phpunit で middleware のテストをする

middleware のテストでポイントになるのはこのへん.

  • ajax の request instance を生成する
  • closure は単に true を返すようにする (これはわかりやすければ何でもいい)

ちょっと解説.

ajax の request instance を生成する

Laravel の Request クラスは ↓コレ を継承している.

  • Symfony\Component\HttpFoundation\Request

↑コレ を利用すると, 期待に沿った request instance を作りやすい.
たとえば, 今回は ajax リクエストを擬似的に作りたいのでこんな具合にする↓

$request = Request::create('/', 'GET');
$request->headers->set('X-Requested-With', 'XMLHttpRequest');

先程の curl の例と同様に, request header に ajax であることを示す値をセットした.
これで擬似的に ajax リクエストのインスタンス $request が完成.

closure は単に true を返すようにする

これ, なんでもいいんだけど私は理解しやすいので true を返す closure を作成した.

Laravel の middleware に記述されている return $next($request); は 「次の処理に行くよー」 って意味.
ここでいう次の処理とは

  • 次の middleware に処理が移る
  • controller に処理が移る

のいずれかの意味.

「この middleware の処理は終わったよー」 という合図として認識しやすいものであればなんでも ok.

こんな考えで, 私は次のようなテストを書いた.

<?php

// tests/SampleBeforeMiddlewareTest.php

use App\Http\Middleware\SampleBeforeMiddleware;
use Illuminate\Http\Request;

class SampleBeforeMiddlewareTest extends TestCase
{
    private $closure;

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

        $this->closure = function () {
            return true;
        };
    }

    public function testRedirect()
    {
        $request = Request::create('/', 'GET');
        $request->headers->set('X-Requested-With', 'XMLHttpRequest');

        $sampleBeforeMiddleware = new SampleBeforeMiddleware();
        $response = $sampleBeforeMiddleware->handle($request, $this->closure);

        $this->assertInstanceOf(SampleBeforeMiddleware::class, $sampleBeforeMiddleware);
        $this->assertSame(301, $response->status());
    }
}

多くの場合, phpunit で実行するテストは1つじゃない.
だから, テスト間で使い回せるように setUp() に closure を定義した.

ここで定義した closure は middleware インスタンスから呼び出された handle() method 第2引数に渡されている.

assertion は次の2点.

  • 期待した middleware の instance が生成されているか
  • response の HTTP Status Code は 301 か

ここまでで ajax に関するテストは完成.

実行

phpunit を実行しようと思うんだけど, 折角なので次のテストも行う.

ajax リクエスト ではなく, 単なる GET リクエストが来たらこの middleware を素通りするテスト

これは closure に設定した返り値 true を見れば良い.
最終的なテストファイルはこれ↓

<?php

// tests/SampleBeforeMiddlewareTest.php

use App\Http\Middleware\SampleBeforeMiddleware;
use Illuminate\Http\Request;

class SampleBeforeMiddlewareTest extends TestCase
{
    private $closure;

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

        $this->closure = function () {
        return true;
        };
    }

    public function testRedirect()
    {
        $request = Request::create('/', 'GET');
        $request->headers->set('X-Requested-With', 'XMLHttpRequest');

        $sampleBeforeMiddleware = new SampleBeforeMiddleware();
        $response = $sampleBeforeMiddleware->handle($request, $this->closure);

        $this->assertInstanceOf(SampleBeforeMiddleware::class, $sampleBeforeMiddleware);
        $this->assertSame(301, $response->status());
    }

    public function testSkipMiddleware()
    {
        $request = Request::create('/', 'GET');
        // $request->headers->set('X-Requested-With', 'XMLHttpRequest');

        $sampleBeforeMiddleware = new SampleBeforeMiddleware();
        $response = $sampleBeforeMiddleware->handle($request, $this->closure);

        $this->assertInstanceOf(SampleBeforeMiddleware::class, $sampleBeforeMiddleware);
        $this->assertSame(true, $response);
    }
}

testSkipMiddleware() はこの middleware を skip するテスト.
ajax リクエストのテストと比べて closure の値を見ているのが特徴.

いざ, 実行.

# cd path/to/your/phpunit/test/directory

$ phpunit ./SampleBeforeMiddlewareTest.php
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

.  .                                                                  2 / 2 (100%)

Time: 176 ms, Memory: 10.00MB

OK (2 tests, 4 assertions)

ok.

まとめ

phpunit で ajax request instance を作るのが最初のハードル.
次のハードルは, closure をよしなに取り扱うこと.

多くの時間を調査に割いたけど, 前者に辿り着くのが大変だった.
結果的に公式ドキュメントに Request インスタンスの親クラスについての記述があったから, ドキュメントをよく読むのは大切だと再認識した.

Laravel の API ドキュメント眺めると発見が多いな.

今回は以上.

スポンサーリンク
336 x 280 – レクタングル(大)
336 x 280 – レクタングル(大)
  • このエントリーをはてなブックマークに追加

この記事が気に入ったら
いいね!しよう

スポンサーリンク
336 x 280 – レクタングル(大)
トップへ戻る