Laravelの更新系APIのテストにおいて、APIのリクエスト手法を間違えたばかりにテストが失敗して悩んだ話を書きます。
ちゃんと公式ドキュメントを読めば当たり前のことなのですが、APIのテストにはjson
メソッドを使いましょう。
APIに関連付けられたフォームリクエストが実行されるとき、
例えばform
タグをsubmit
して、バリデーションエラーが発生したら入力画面にリダイレクトしてほしい。
また、AJAXのAPIとしてリクエストし、バリデーションエラーならばJSONでエラーメッセージを返してほしい。
HTTPステータスコードでいうと、リダイレクトは302、バリデーションエラーのJSONレスポンスは422となります。
Laravelでバリデーションするときに使われるフォームリクエストは、この辺りの処理を自動でやってくれます。ありがたい
公式ドキュメントにもちゃんと書かれています。
If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.
そのHTTPステータスコードの切り替えに関わるAPIの呼び出し方をテストで再現しなかったばかりにテストが失敗して悩んでしまいました。
再現するために、ちゃちゃっとLaravelで更新系のAPIを作ってみます。使うLaravelのバージョンは6.xです。
$ composer create-project --prefer-dist laravel/laravel apisample
$ cd apisample
$ php artisan make:request UpdateUserNameRequest
$ php artisan make:controller UpdateUserNameApi
$ php artisan make:test UpdateUserNameApiTest
<?php
use Illuminate\Http\Request;
Route::patch('/update_user_name', 'UpdateUserNameApi');
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserNameRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
];
}
}
名前は必須だよというフォームリクエストです。
<?php
namespace App\Http\Controllers;
use App\Http\Requests\UpdateUserNameRequest;
class UpdateUserNameApi extends Controller
{
public function __invoke(UpdateUserNameRequest $request)
{
// 更新処理
return response()->json(['message' => '更新完了']);
}
}
とりあえずの更新APIなので、どのユーザの名前を更新するんやい!! という点は置いといてください
次にテストです。まずは私が最初に書いて失敗したテストです。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UpdateUserNameApiTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testApi()
{
// エラー
$this->patch('/api/update_user_name', [])->assertStatus(422);
// 成功
$this->patch('/api/update_user_name', ['name' => 'Takashi'])->assertStatus(200);
}
}
patch
でリクエストして、エラーだと422を想定してassertStatus(422)
してます。
これを実行すると、
$ vendor/bin/phpunit tests/Feature/UpdateUserNameApiTest.php
PHPUnit 8.5.0 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 383 ms, Memory: 18.00 MB
There was 1 failure:
1) Tests\Feature\UpdateUserNameApiTest::testApi
Expected status code 422 but received 302.
Failed asserting that false is true.
apisample/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:183
apisample/tests/Feature/UpdateUserNameApiTest.php:19
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
422じゃなくて302だよと言われ、テストが失敗してしまいました。
よくよく考えると、このテストで実行しているリクエストはAJAXリクエストではありません。
なので422ではなくて、リダイレクトの302が返ってくるわけです。
それじゃあ、実際の運用と同じようにAPIにリクエストするにはどうしたらよいかというと、公式ドキュメントにしっかり載っていました。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UpdateUserNameApiTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testApi()
{
// エラー
$this->patchJson('/api/update_user_name', [])->assertStatus(422);
// 成功
$this->patchJson('/api/update_user_name', ['name' => 'Takashi'])->assertStatus(200);
}
}
$ vendor/bin/phpunit tests/Feature/UpdateUserNameApiTest.php
PHPUnit 8.5.0 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 112 ms, Memory: 18.00 MB
OK (1 test, 2 assertions)
公式ドキュメントをちゃんと読みましょう自分