Laravelにおいて、あるルートはAjaxの場合のみ通したい、
さらにphpunitでAjaxのルートもテストしたい、という状況が発生したため、
その方法を考えてみました。
(確認環境:Laravel5.4)
Ajaxのみ許可するルート
\Illuminate\Http\Request
クラスには、ajax()
というメソッドが用意されています。
そのリクエストがAjax通信か否かをtrue``false
で返してくれます。
if ($request->ajax()) {
// Ajaxである!
} else {
// Ajaxではない
}
また、ルートに対して何らかの制限やフィルターをかける場合、ミドルウェアを使うのが良さげです。
ということで、上記メソッドとミドルウェアを使ってAjax通信以外を遮断してみます。
php artisan make:middleware CheckAjax
public function handle($request, Closure $next)
{
// Ajaxアクセスでなければ404
abort_unless($request->ajax(), 404);
return $next($request);
}
protected $routeMiddleware = [
// ...
'ajax' => \App\Http\Middleware\CheckAjax::class, // CheckAjaxミドルウェアの短縮キーを登録
// ...
];
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()
がどういう基準で判定しているか、コードを追ってみました。
public function ajax()
{
return $this->isXmlHttpRequest();
}
isXmlHttpRequest()
というメソッドの別名になってます。
isXmlHttpRequest()
の方は
public function isXmlHttpRequest()
{
return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
}
となっており、ヘッダーのX-Requested-With
の値がXMLHttpRequest
であれば、
Ajax通信であると判定しているみたいです。
テストコードで使っている$this->get()
の実装は以下の通りです。
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
クラスを継承しているようなので、
ここにメソッドを追加してみました。
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
とか使ったらもっとスマートになるかもしれません。