CRUD API@laravel8で、テストコードを書いてみた
1, 最初からあるサンプルのテストコードを実行してみる。
windowsだと区切り文字が円マークなので注意!
1 2 3 4 5 6 7 8 9 |
vendor\bin\phpunit tests\Feature\ExampleTest.php PHPUnit 9.5.10 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.140, Memory: 20.00 MB OK (1 test, 1 assertion) |
laravelなら、php artisan test が楽。全テストを実行する!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
php artisan test Warning: TTY mode is not supported on Windows platform. PASS Tests\Unit\ExampleTest ✓ example PASS Tests\Feature\ExampleTest ✓ example PASS Tests\Feature\StationTest ✓ example Tests: 3 passed Time: 0.25s |
2. 何をテストしているかコードを見てみる
laravelプロジェクトのルートにアクセスして、HTTPレスポンスが200かどうかをテストしているようだ
tests/Feature/ExampleTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ExampleTest extends TestCase { /** * A basic test example. * * @return void */ public function test_example() { $response = $this->get('/'); $response->assertStatus(200); } } |
2. わざと失敗するようなコードを書いてみる
1 2 3 4 5 6 7 8 9 10 |
public function test_example() { $response = $this->get('/'); $response->assertStatus(200); $response = $this->get('/abc'); $response->assertStatus(200); // $response->assertStatus(404); } |
ちゃんと失敗した。$response->assertStatus(404);だと成功になる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
c:\xampp\htdocs\laravel8>vendor\bin\phpunit tests\Feature\ExampleTest.php PHPUnit 9.5.10 by Sebastian Bergmann and contributors. F 1 / 1 (100%) Time: 00:00.144, Memory: 20.00 MB There was 1 failure: 1) Tests\Feature\ExampleTest::test_example Expected response status code [200] but received 404. Failed asserting that 200 is identical to 404. C:\xampp\htdocs\laravel8\vendor\laravel\framework\src\Illuminate\Testing\TestResponse.php:177 C:\xampp\htdocs\laravel8\tests\Feature\ExampleTest.php:22 FAILURES! Tests: 1, Assertions: 2, Failures: 1. |
3. 駅のCRUD APIのテストコードを書いてみる
デフォルトでtest_example()があるが、新規にtest_xxxxxxx()を追加すれば、自動的にテストしてくれる
1 |
php artisan make:test StationTest |
tests/Feature/StationTest.phpが生成される
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class StationTest extends TestCase { public function test_example() { $response = $this->get('/'); $response->assertStatus(200); } } |
4. 自作テストを実行してみる。問題なく実行できる。
1 2 3 4 5 6 7 8 9 |
vendor\bin\phpunit tests\Feature\StationTest.php PHPUnit 9.5.10 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.140, Memory: 20.00 MB OK (1 test, 1 assertion) |
5. 実際にテストしたい内容を書いてテストしてみる。
1 2 3 4 5 6 |
public function test_example() { $response = $this->get('/api/station'); $response->assertStatus(200); } |
1 |
vendor\bin\phpunit tests\Feature\StationTest.php |
6. 真面目にCRUD APIのテストコードを書いてみる。
a. INSRT(新規作成)
b. SHOW(新規作成したレコードの詳細表示)
c. UPDATE(新規作成したレコードを更新)
b. SELECT(更新したレコードを検索)
e. DELETE(作ったレコードを削除)
これで、一通りのCRUD API動作テストは出来るかな?
7. INSRT(新規作成)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class StationTest extends TestCase { /** * A basic feature test example. * * @return void */ public function test_example() { // APIパスチェック $base_url ='/api/station'; $response = $this->get($base_url); $response->assertStatus(200); // テスト駅を生成(INSERT) $response = $this->post($base_url, [ 'line_id' => 1, 'name' => 'テスト駅', ]); $response->assertStatus(200); } } |
8. 新規作成は問題なく終わったけど、戻り値のJSONチェックがしたい!
なんなら、CRUD APIを全部テストして欲しい!
一気にコーディングして、リファクタリングしてみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; use Illuminate\Testing\Fluent\AssertableJson; //追加 use Illuminate\Testing\TestResponse; //追加 class StationTest extends TestCase { public const TEST_LINE_ID = 1; // DB登録済の路線ID public const TEST_LINE_ID_NAME = '山手線'; // 対応する路線名 public const TEST_STATION_NAME = 'テスト駅'; // テスト用の駅名 public const ENRTY_POINT_URL = '/api/station'; // APIのエントリーポイント public function test_example() { // 駅を新規作成 $id = self::insert(); // 駅の詳細表示 self::show($id); // 駅名を更新 $update_test_name = self::update($id); // 駅名で検索 self::search($update_test_name); // 駅をid削除。駅名も確認。delete()は予約語で使えなかった…。 self::station_delete($id, $update_test_name); } private function insert() { // テスト駅を生成(INSERT) $response = $this->post(self::ENRTY_POINT_URL, [ 'line_id' => self::TEST_LINE_ID, 'name' => self::TEST_STATION_NAME, ]); // APIの戻り値JSONをチェック self::check_response($response, 'Insert'); // 生成したレコードIDを返す return $response['details'][0]['id']; } private function show($id) { // テスト駅の詳細(Show) $response = $this->get(self::ENRTY_POINT_URL. "/$id"); // APIの戻り値JSONをチェック self::check_response($response, 'Show', $id); } private function update($id) { // テスト駅の更新(UPDATE) $update_test_name = self::TEST_STATION_NAME.mt_rand(); // 駅名がユニークになるように乱数を付与 $response = $this->put(self::ENRTY_POINT_URL. "/$id", [ 'name' => $update_test_name, ]); // APIの戻り値JSONをチェック self::check_response($response, 'Update', $id, $update_test_name); // 変更した駅名 return $update_test_name; } private function search($update_test_name) { // テスト駅の検索(Searching) $response = $this->post(self::ENRTY_POINT_URL.'/search', [ 'keyword' => $update_test_name, // 更新した駅名で検索 ]); // APIの戻り値JSONをチェック self::check_response($response, 'Search', NULL, $update_test_name); } private function station_delete($id, $update_test_name) { // テスト駅の削除(DELETE) $response = $this->delete(self::ENRTY_POINT_URL. "/$id"); // APIの戻り値JSONをチェック self::check_response($response, 'Delete', NULL, $update_test_name); } // テストコードだと Illuminate\Http\Response じゃない!! // JSONの戻り値は違うので、修正が必要!! private function check_response(TestResponse $response, $message, $id = NULL, $update_test_name = NULL) { $response->assertStatus(200); // 型のチェック $response->assertJson(fn (AssertableJson $json) => $json->whereType('success', 'boolean') ->whereType('summary', 'string') ->whereType('details', 'array') ->whereAllType([ 'details.0.id' => 'integer', 'details.0.name' => 'string', 'details.0.lines.0.id' => 'integer', 'details.0.lines.0.name' => 'string', ]) ); // 値のチェック $response ->assertJsonPath('success', true) ->assertJsonPath('summary', "$message Success!") ->assertJsonPath('details.0.lines.0.id', self::TEST_LINE_ID) ->assertJsonPath('details.0.lines.0.name', self::TEST_LINE_ID_NAME) ; // レコードIDが指定されていたらチェックする if($id !== NULL){ $response->assertJsonPath('details.0.id', $id); } // 駅名のupdate後で、チェックする駅名を変更 if($update_test_name === NULL){ $response->assertJsonPath('details.0.name', self::TEST_STATION_NAME); }else{ $response->assertJsonPath('details.0.name', $update_test_name); } } } |
基本のCRUD APIのテストは、こんな感じで行けそうだ!