はじめに
テスト用のDB作成やテスト用のファイル作成が済んでいる前提で解説します。
また、業務でFeatureテストを書く事がありますが、果たしてこの書き方でいいのか確信が持てない為、ご意見を頂けると嬉しいです。
目次
- テスト対象のコード
- テスト用のダミーデータ作成
- Featureテスト( index )
- Featureテスト( create )
- Featureテスト( store )
- Featureテスト( show )
- Featureテスト( edit )
- Featureテスト( update )
- Featureテスト( destroy )
- 参考文献
テスト対象のコード
こんな感じのクソショボい処理のFeatureテストを書いていきます。
Qiitaみたいに記事一覧を閲覧したり、投稿、更新、削除等の機能がある処理のテストコードを例に書いていきます。
ルーティング
routes/user.php (ルーティングファイル)
Route::resource('post', PostController::class);
コントローラー
PostController.php
public function index()
{
$posts = Post::get();
return view('user.post.index', ['posts' => $posts]);
}
〜 間の処理は省略します(create, store, show, edit, update) 〜
public function destroy(Post $post)
{
$post->delete();
return redirect()->action([PostController::class,'index']);
}
テスト用のダミーデータ作成
コマンドでテスト用のファイルを作成後、こちらにテストで使用するダミーデータを用意します。
// tests/Feature/User/PostControllerTest.phpファイル
// RefreshDatabaseをuse宣言
use Illuminate\Foundation\Testing\RefreshDatabase;
// 使用するモデルを追記
use App\Models\User;
use App\Models\Project;
class PostControllerTest extends TestCase
{
// RefreshDatabaseにて、DBのデータを各テスト毎にリセット
use RefreshDatabase;
Public function setUp(): void
{
parent::setUp();
// UserFactory()で定義したユーザーのダミーデータを1件作成
$this->user = User::factory()->create();
// PostFactory()で定義したユーザーのダミーデータを1件作成
$this->post = Post::factory()->create();
// store, update処理で使用するRequestパラメーター
// パラメータの値としては上記で作成したダミーデータを使用する。
$this->data =
[
// 上記で作成した$this->userのid
'user_id' => $this->user->id,
'title' => $this->post->title,
'content' => $this->post->content,
];
}
【各ヘルパメソッド】
・setUp
詳しくは下の参考文献を参照ください。
・RefreshDatabase
毎回テストメソッドを実行する度にテスト用DBをリセットして、マイグレーションを実行しています。
※マイグレーションのfreshをやってます。
IDのオートインクリメントはリセットされない為、ダミーデータのidはテストを繰り返す毎に増えていきます。
indexアクションテスト
テスト対象のindexアクション
TOP画面から投稿一覧画面('user.index')から、投稿一覧画面('user.post.index')に遷移する処理。
public function index()
{
$posts = Post::get();
return view('user.post.index', ['posts' => $posts]);
}
__indexアクションテスト__
・先程のダミーデータの下にテストを書いていきます。
・testIndex( )と書いていますが、テストメソッドの先頭に'test'を付けて、その後の文字は任意で設定して下さい。
※アノテーション(@test)をつける方法もありますので、こちらの方法で命名をしたい方はこちらを参照ください。
https://qiita.com/apricotcomic/items/76a5c4cf63dbd0e1b39c
// tests/Feature/User/PostControllerTest.phpファイル
class ReportControllerTest extends TestCase
{
public function testIndex()
{
// エラー内容を分かりやすくしてくれる(Laravel8.51以降では不要・・・らしいです)
$this->withoutExceptionHandling();
// TOPページから画面遷移
$response = $this->from(route('user.index'))
// 投稿一覧ページに画面遷移
->get(route('user.post.index'));
// テスト成功 (HTTPレスポンス200)
$response->assertOk()
// ルートによって指定されたビューページが返されているか確認
->assertViewIs('user.post.index')
// ビューに渡すデータが合っているか確認
->assertViewHas(['posts']);
}
【各ヘルパメソッド】
・withoutExceptionHandling()
Laravel8.51以前までは、テストで失敗した際にエラーメッセージを分かりやすく表示してくれていましたが、Laravel8.51以降からは別に付けなくてもエラー原因が表示されるみたいです。
【参考記事】
https://zenn.dev/nshiro/articles/a00dea5f688cf9
特定の例外が投げられているかテストしたい時には使えるらしいです。
・from( )
どのページから遷移するのか設定できます。引数に遷移元のルートを記載します。
・get( )
リクエストパラメーターのgetです。storeではpost、updateではput、destroyではdeleteを使用します。
今回はindexメソッドで投稿一覧にアクセスするのでgetメソッドを使用します。
【assertメソッド】
・assertOk( )
HTTPレスポンスで200が返ってきた場合にテスト成功となります。
逆に200以外が返ってきた場合はテスト失敗となります。
※assertStatus(200)と同様の意味になります。
・assertViewIs( )
遷移先のページがviews/posts/index.blade.phpである事を確認します。
違うページに遷移していたら、テスト失敗となります。
・assertViewHas( )
ビューに$postsのデータが渡されているかチェックします。
#createアクションテスト
テスト対象のcreateアクション
ログインしたユーザーのみ投稿一覧画面('user.post.index')から、新規投稿画面('user.post.create')に遷移する処理。
※index以外の処理はログインしていないと操作できない仕様とします。
public function create()
{
return view('user.post.create');
}
__createアクションテスト__
public function testCreate()
{
$this->withoutExceptionHandling();
// ログインユーザーで画面遷移
$response = $this->actingAs($this->user)
->from(route('user.post.index'))
->get(route('user.post.create'));
$response->assertOk()
->assertViewIs('user.post.create');
}
【各ヘルパメソッド】
・actingAs( )
ログイン中のユーザーしか閲覧できないページがある場合に使用します。
引数に先程作成したダミーデータのユーザーを入れる事で、擬似的にそのユーザーがログインしている状況を作ります。
#storeアクションテスト
テスト対象のstoreアクション
新規投稿画面('user.post.create')から投稿後、投稿一覧画面('user.post.index')に遷移する処理。
public function store(Request $request)
{
$post = new Post;
$post->title = request('title');
$post->content = request('content');
$post->save();
return redirect()->action([PostController::class,'index']);
}
__storeアクションテスト__ ・リクエストパラメーターはpostの点に注意して下さい。 ・$this->dataのダミーデータを用いてDB登録処理を行う為、記述して下さい。
public function testStore()
{
$this->withoutExceptionHandling();
$response = $this->actingAs($this->user)
->from(route('user.post.create'))
// リクエストパラメーターpost
// $this->data => storeの$requestのデータにあたる部分
->post(route('user.post.store'), $this->data);
// 指定したURIへリダイレクトする。
$response->assertRedirect(route('user.post.index', ['posts' => $this->posts]));
}
【各ヘルパメソッド】
・post( )
リクエストパラメーターのpostです。store処理で使用される事が多いです。
【assertメソッド】
・assertRedirect( )
引数に指定したURI通りにリダイレクトしているか確認します。
#showアクションテスト
テスト対象のshowアクション
投稿一覧画面('user.post.index')から、投稿詳細画面(user.post.show)に画面遷移する処理。
public function show(Post $post)
{
return view('user.post.show', ['post' => $post]);
}
__showアクションテスト__ ルートモデルバインディングから受け取った$postをviewに渡す為、書き忘れないように注意して下さい。
public function testShow()
{
$this->withoutExceptionHandling();
$response = $this->actingAs($this->user)
->from(route('user.post.index'))
// ルートモデルバインディングから受け取った$postをviewに渡す
->get(route('user.post.show'), ['post' => $this->post]);
$response->assertOk()
->assertViewIs('user.post.show')
->assertViewHas('post');
}
#editアクションテスト
テスト対象のeditアクション
投稿一覧画面('user.post.index')から、投稿更新画面(user.post.edit)に画面遷移する処理。
public function edit(Post $post)
{
return view('user.post.edit', ['post' => $post]);
}
__editアクションテスト__ showアクションのテストとほぼ同じ処理を書きます。
public function testEdit()
{
$this->withoutExceptionHandling();
$response = $this->actingAs($this->user)
->from(route('user.post.index'))
->get(route('user.post.edit'), ['post' => $this->post]);
$response->assertOk()
->assertViewIs('user.post.edit')
->assertViewHas('post');
}
#updateアクションテスト
テスト対象のupdateアクション
投稿更新画面('user.post.edit')から投稿を更新後、投稿一覧画面('user.post.index')に遷移する処理。
public function update(Request $request, Post $post)
{
$post = Post::find($request->id);
$post->title = request('title');
$post->content = request('content');
$post->save();
return redirect()->action([PostController::class,'index']);
}
__updateアクションテスト__ ・リクエストパラメーターがputである点に注意が必要です。 ・store処理同様リクエストデータ($this->data)を書き忘れないでください。
public function testUpdate()
{
$this->withoutExceptionHandling();
$response =
$this->actingAs($this->user)
// editからなので、ルートモデルバンディングのパラメータを忘れずに!
->from(route('user.post.edit'),['post' => $this->post])
// リクエストパラメーターput
// $this->dataを書き忘れないように!
->put(route('user.post.update') ,['post' => $this->post]), $this->data);
$response
->assertRedirect(route('user.post.index', ['posts' => $this->posts]));
}
【各ヘルパメソッド】
・put( )
リクエストパラメーターのputです。update処理で使用されます。
#destroyアクションテスト
テスト対象のdestroyアクション
投稿一覧画面('user.post.index')で投稿を削除('user.post.destroy')し、投稿一覧画面('user.post.index')にリダイレクトする処理。
public function destroy(Post $post)
{
$post->delete();
return redirect()->action([PostController::class,'index']);
}
__destroyアクションテスト__ ・リクエストパラメーターはdelete ・今回はソフトデリートを想定している為、assertSoftDeleted( )やassertEquals( )を使用しています。 ソフトデリートでなければ、assertDeleted( )やassertDatabaseMissing( )を使用するかと思います。
public function testDestroy()
{
$this->withoutExceptionHandling();
$response =
$this->actingAs($this->user)
->from(route('user.post.index'))
// リクエストパラメーターは delete
->delete(route('user.post.destroy'),['post' => $this->post]);
$response->assertRedirect(route('user.post.index', ['posts' => $this->posts]));
// レコードが削除されたことを確認
// 注意!! 以下ソフトデリート用の処理。
$this->assertSoftDeleted($this->post);
->assertEquals(0, Post::count());
}
【各ヘルパメソッド】
・delete( )
リクエストパラメーターのdeleteです。delete処理で使用されます。
【assertメソッド】
・assertSoftDeleted( )
引数に設定しているレコードがソフトデリートされたか確認します。
・assertEquals( )
第一引数と第二引数の値が同じであるか確認する。
今回の場合はDBにあるデータが空になっていれば、テスト成功です。
・assertDeleted( )
引数に設定しているレコードがデリートされたか確認します。
※ソフトデリートの場合assertSoftDeletedを使用します。
・assertDatabaseMissing( )
DBに該当するレコードが無いかどうかを確認します。
第一引数にテーブル名、第二引数に該当するレコードを設定します。
※こちらはソフトデリートの場合使用できません。
下記記事を参考にして下さい。
https://techfree.jp/devmemo/devmemo-2696-3