LoginSignup
36
21

More than 3 years have passed since last update.

LaravelでAjaxかどうか判定し、自動テストにも対応させる

Last updated at Posted at 2017-04-26

Laravelにおいて、あるルートはAjaxの場合のみ通したい、
さらにphpunitでAjaxのルートもテストしたい、という状況が発生したため、
その方法を考えてみました。
(確認環境:Laravel5.4)

Ajaxのみ許可するルート

\Illuminate\Http\Requestクラスには、ajax()というメソッドが用意されています。
そのリクエストがAjax通信か否かをtruefalseで返してくれます。

if ($request->ajax()) {
    // Ajaxである!
} else {
    // Ajaxではない
}

また、ルートに対して何らかの制限やフィルターをかける場合、ミドルウェアを使うのが良さげです。
ということで、上記メソッドとミドルウェアを使ってAjax通信以外を遮断してみます。

ミドルウェア追加
php artisan make:middleware CheckAjax
app/Http/Middleware/CheckAjax.php を修正
    public function handle($request, Closure $next)
    {
        // Ajaxアクセスでなければ404
        abort_unless($request->ajax(), 404);

        return $next($request);
    }
app/Http/Kernel.php ミドルウェア短縮キー追加
    protected $routeMiddleware = [
        // ...

        'ajax' => \App\Http\Middleware\CheckAjax::class, // CheckAjaxミドルウェアの短縮キーを登録

        // ...
    ];
routes/we.php ルートに登録
    Route::get('/only/ajax', 'OnlyController@ajax')->middleware('ajax');

これで、/only/ajaxというルートに関しては、Ajaxによる通信以外は404エラーを返すようになりました。

試しにブラウザで/only/ajaxにアクセスしてみて、404になれば成功です。

AjaxなルートをHttpテストする

LaravelにはHttpリクエストをテストする機能が存在していますが、
前述のAjaxによる通信制限が入っているルートをテストしようとしても、
普通にやると制限に引っかかって404になってしまいます。

$response = $this->get('/only/ajax');
$response->assertStatus(200); // ajaxと判定されずに404になり、失敗

なんとかしてAjaxの通信であると判定させる必要があります。
そこで、まずは$request->ajax()がどういう基準で判定しているか、コードを追ってみました。

vendor\laravel\framework\src\Illuminate\Http\Request.php
    public function ajax()
    {
        return $this->isXmlHttpRequest();
    }

isXmlHttpRequest()というメソッドの別名になってます。
isXmlHttpRequest()の方は

vendor\laravel\framework\src\Illuminate\Http\Request.php
    public function isXmlHttpRequest()
    {
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
    }

となっており、ヘッダーのX-Requested-Withの値がXMLHttpRequestであれば、
Ajax通信であると判定しているみたいです。

テストコードで使っている$this->get()の実装は以下の通りです。

vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.php
    public function get($uri, array $headers = [])
    {
        $server = $this->transformHeadersToServerVars($headers);

        return $this->call('GET', $uri, [], [], [], $server);
    }

第一引数が$uri、第二引数が$headersになってます。
第二引数にリクエストヘッダが指定できるようです。
ということで、第二引数にX-Requested-With: XMLHttpRequestをセットしてみます。

$response = $this->get('/only/ajax', ['X-Requested-With' => 'XMLHttpRequest']);
$response->assertStatus(200); // Ajaxと判断され、成功

無事、Ajaxとしてリクエストを処理してくれました。

共通で使えるようにする

毎回毎回第二引数に['X-Requested-With' => 'XMLHttpRequest']と書くのは面倒なので、
共通で使えるようにしてみます。

追加したテストケースは必ずTestCaseクラスを継承しているようなので、
ここにメソッドを追加してみました。

tests\TestCase.php
abstract class TestCase extends BaseTestCase
{
    // ...

    protected function getAjax($uri, array $headers = [])
    {
        $headers['X-Requested-With'] = 'XMLHttpRequest';
        return $this->get($uri, $headers);
    }
}

これをget()と置き換えれば、勝手にAjax用のヘッダーを付与してGETリクエストしてくれるようになります。

$response = $this->getAajx('/only/ajax');
$response->assertStatus(200); // 200で成功

get()の他にもjson()get()post()put()patch()delete()
といったメソッドが用意されているようなので、それぞれに対してxxAjaxを用意すると良いと思います。

また、今回は基底クラスにメソッドを追加しましたが、
traitとか使ったらもっとスマートになるかもしれません。

36
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
21