今回は、LaravelのControllerテストのコードをテンプレとしてまとめておきます。(ほとんど車輪の再発明をしないための自分用メモです)
テストコードの書き方の一例として参考になれば幸いです。
※Laravelはバージョン6系を使用しています
Laravelコントローラテストのテンプレまとめ
今回は以下の3つのControllerについてテストを書いてみました。
- PostControllerTest
- RegisterControllerTest
- LoginControllerTest
それぞれ多くのテストケースを書いていますが、基本的な考え方はどれも同じです。
要は、Controllerのアクションに対してRequestを投げて、正しい(予想通りの)Responseが返ってくるかを判定しているだけです。難しく考える必要はありません。
それでは、それぞれのControllerについてテストケースの例を掲載します。
PostControllerTestのテストケース
PostControlerTestでは、一般的な投稿のCRUD処理に関するテストを書いています。
何も説明しないのもあれなので、簡単に概要だけ解説しますね。
今回、テスト用データはファクトリで生成しました。
また、各テストケースごとにDBを初期化するため、DatabaseMigrationsトレイトを設置しています。
最初はRefreshDatabaseトレイトを使っていたのですが、RefreshDatabaseを使った場合、オートインクリメントがリセットされないことに気付いたため、変更しました。
具体的なテストの流れは、どのテストケースも似ています。こんな感じです。
- ファクトリでテストユーザーを作成
- actingAsでユーザーを認証させる
- Controllerに対してHTTPリクエストを投げ、Response(結果)を受け取る
- それぞれのテストケースに対して適切なアサーションを利用して、Response結果の成否を判定する
ちなみに、上記はコントローラテストの場合ですが、より一般的なテストの場合に拡張するとこんな感じになります。
- テストを実行するための条件(ユーザ認証、データをインサートなど)を設定
- テスト対象を実行(HTTPリクエストを送る、クラスのメソッドを実行するなど)
- 実行結果を検証(アサーションを利用)
全てのテストの前に書かれてある/** @test */はアノテーションと言い、これを書いておくことで、日本語でテストメソッドの名前を指定することができます。詳しくはこちらの記事を参考にして下さい。
概要は以上です。ここまで理解しておけば、おそらく以下のコードは読めると思います。
<?php
namespace Tests\Http\Controllers;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;
use App\User;
use App\Post;
class PostControllerTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function 投稿一覧のURLにアクセスして画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->get(action('PostController@index'));
$response->assertStatus(200);
}
/** @test */
public function 投稿一覧のURLにアクセスして投稿一覧画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->get(action('PostController@index'));
$response->assertViewIs('layouts.home');
}
/** @test */
public function ログインしていない場合は投稿一覧のURLにアクセスしてもログイン画面が表示される()
{
$response = $this->get(action('PostController@index'));
$response->assertRedirect(route('login'));
}
/** @test */
public function 投稿一覧画面に全ての投稿データが表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$firstPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'firstPost'
]);
$secondPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'secondPost'
]);
$thirdPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'thirdPost'
]);
$response = $this->get(action('PostController@index'));
$response->assertSee($firstPost->content, $thirdPost->content, $secondPost->content);
}
/** @test */
public function 投稿一覧画面に作成日時の降順で投稿が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$firstPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'firstPost',
'created_at'=> '2020-06-03 07:29:56'
]);
$secondPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'secondPost',
'created_at'=> '2020-06-03 07:30:59'
]);
$thirdPost = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'thirdPost',
'created_at'=> '2020-06-03 07:34:59'
]);
$response = $this->get(action('PostController@index'));
$response->assertSeeInOrder($expects = [$thirdPost->created_at->diffForHumans(), $secondPost->created_at->diffForHumans(), $firstPost->created_at->diffForHumans()]);
}
/** @test */
public function 投稿一覧画面で投稿に紐付く投稿者名が表示される()
{
factory(User::class, 2)->create();
// ユーザーのうち片方を認証させる
$user = User::findOrFail(1);
$this->actingAs($user);
// 認証しているユーザーが投稿を送信し保存
$data = ['content' => 'test'];
$this->post(route('posts.store'), $data);
// 投稿一覧画面で認証しているユーザーの名前が表示されているか確認
$userName = $user->name;
$this->get(action('PostController@index'))->assertSee($userName);
}
/** @test */
public function 投稿作成のURLにアクセスして画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->get(route('posts.create'));
$response->assertStatus(200);
}
/** @test */
public function 投稿作成のURLにアクセスして投稿画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->get(action('PostController@create'));
$response->assertViewIs('layouts.post');
}
/** @test */
public function データを投稿し保存する()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$data = ['content' => 'storeTest'];
$response = $this->post(route('posts.store'), $data);
$this->assertDatabaseHas('posts', [
'content' => 'storetest'
]);
}
/** @test */
public function 保存処理完了後は投稿一覧画面に遷移する()
{
$data = ['content' => 'storeTest'];
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->post(route('posts.store'), $data);
$response->assertRedirect(action('PostController@index'));
}
/** @test */
public function 投稿フォームに何も投稿しなかった場合はバリデーションメッセージが表示される()
{
$data = ['content' => null];
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->post(route('posts.store'), $data);
$validation = '投稿内容は必ず指定してください';
$this->get(route('posts.create'))->assertSee($validation);
}
/** @test */
public function 詳細画面のURLにアクセスして画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'showTest'
]);
$response = $this->get(route('posts.show', $item->id));
$response->assertStatus(200);
}
/** @test */
public function 詳細画面のURLにアクセスして投稿の詳細画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'showTest'
]);
$response = $this->get(route('posts.show', $item->id));
$response->assertViewIs('layouts.item');
}
/** @test */
public function 存在しない投稿の詳細を表示させようとするとエラー画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'showTest'
]);
$response = $this->get(route('posts.show', 10));
$response->assertStatus(404);
}
public function 詳細画面には特定の投稿が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'showTest'
]);
$item = Post::find($item->id);
$response = $this->get(route('posts.show', $item->id));
$response->assertViewHas('item', $item);
}
/** @test */
public function 詳細画面で投稿に紐付く投稿者名が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'showTest'
]);
$UserName = $user->name;
$item = Post::find($item->id);
$response = $this->get(route('posts.show', $item->id));
$response->assertSee($UserName);
}
/** @test */
public function 編集画面のURLにアクセスして画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'edittest'
]);
$response = $this->get(route('posts.edit', $item->user_id));
$response->assertStatus(200);
}
/** @test */
public function 編集画面のURLにアクセスして投稿編集画面が表示される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'edittest'
]);
$response = $this->get(route('posts.edit', $item->user_id));
$response->assertViewIs('layouts.edit');
}
/** @test */
public function 投稿した内容の更新をする()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'editTest'
]);
factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'NotEdit'
]);
$data = ['content'=>'投稿を編集しました'];
$response = $this->put(route('posts.update', $item->id), $data);
$this->assertDatabaseHas('posts', [
'content' => '投稿を編集しました'
]);
}
/** @test */
public function データを更新した後は投稿の詳細画面に遷移する()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'editTest'
]);
$data = ['content'=>'testPost'];
$response = $this->put(route('posts.update', $item->id), $data);
$response->assertRedirect(action('PostController@show', $item->id));
}
/** @test */
public function 投稿を削除する()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'deletepost'
]);
$this->delete(route('posts.destroy', $item->id));
$this->assertSoftDeleted('posts', [
'user_id' => $user->id,
'content' => 'deletepost'
]);
}
/** @test */
public function 投稿の削除処理完了後は投稿一覧画面に遷移する()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$item = factory(Post::class)->create([
'user_id' => $user->id,
'content' => 'deletepost'
]);
$response = $this->delete(route('posts.destroy', $item->id));
$response->assertRedirect(action('PostController@index'));
}
}
正直ここまで細かく書く必要はない気がします。(画面を確認すれば分かるのもあるし)
ただ、今回はあくまでテンプレ集なので、なるべく細かく書いてみました。
実際にテストを書くときは、ここからピックアップしたものを書くので十分かなと思います。(もちろん現場の方針にもよりますが)
RegisterControllerTestのテストケース
次に、ユーザー登録のControllerに対するテストです。
書き方としては、PostControllerTestの時とほとんど同じです。
しかし、この場面では、まだユーザー登録やログイン(認証処理)をしていないため、ユーザー生成や認証(actingAs)のコードは必要ありません。
<?php
namespace Tests\Http\Controllers;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;
use App\User;
class RegisterControllerTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function ユーザー登録画面のURLにアクセスしてユーザー登録画面が表示される()
{
$response = $this->get(route('register'));
$response->assertViewIs('auth.register');
}
/** @test */
public function ユーザー登録に成功した後は投稿一覧画面が表示される()
{
// ユーザー登録処理
$response = $this->post(route('register'), [
'name' => 'testUser',
'email' => 'test@example.com',
'password' => 'registerPass',
'password_confirmation' => 'registerPass'
]);
$response->assertRedirect(action('PostController@index'));
}
/** @test */
public function 名前を入力しないで登録しようとするとエラーメッセージが表示される()
{
$response = $this->post(route('register'), [
'name' => '',
'email' => 'test@example.com',
'password' => 'password123'
]);
$errorMessage = '名前は必ず指定してください';
$this->get(route('register'))->assertSee($errorMessage);
}
/** @test */
public function メールアドレスを入力しないで登録しようとするとエラーメッセージが表示される()
{
$response = $this->post(route('register'), [
'name' => 'testuser',
'email' => '',
'password' => 'password123'
]);
$errorMessage = 'メールアドレスは必ず指定してください';
$this->get(route('register'))->assertSee($errorMessage);
}
/** @test */
public function パスワードを入力しないで登録しようとするとエラーメッセージが表示される()
{
$response = $this->post(route('register'), [
'name' => 'testuser',
'email' => 'test@example.com',
'password' => ''
]);
$errorMessage = 'パスワードには正しい形式を指定してください';
$this->get(route('register'))->assertSee($errorMessage);
}
}
主に、きちんと画面が表示されるかと、エラーメッセージが正しく表示されるかをチェックしました。
LoginControllerTestのテストケース
ユーザーのログインに関するControllerのテストです。
先ほどのRegisterControllerTestと同様、この場面では、まだユーザー登録やログイン(認証処理)をしていないため、ユーザー生成や認証(actingAs)のコードは必要ありません。
その他の書き方は、他のControllerのテストとほとんど同じです。
HTTPリクエストをコントローラに対して投げて、予想通りの結果が返ってくるかを判定しています。
<?php
namespace Tests\Http\Controllers;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;
use App\User;
class LoginControllerTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function ログイン画面のURLにアクセスして画面が表示される()
{
$response = $this->get(route('login'));
$response->assertStatus(200);
}
/** @test */
public function ログイン画面のURLにアクセスしてログイン画面が表示される()
{
$response = $this->get(route('login'));
$response->assertViewIs('auth.login');
}
/** @test */
public function 登録しておいたemailアドレスとパスワードでログインできる()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginPass')
]);
// ログイン処理
$response = $this->post(route('login'), [
'email' => 'pass@example.com',
'password' => 'loginPass'
]);
$this->assertAuthenticatedAs($user);
}
/** @test */
public function ログインに成功した後は投稿一覧画面が表示される()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginPass')
]);
// ログイン処理
$response = $this->post(route('login'), [
'email' => 'pass@example.com',
'password' => 'loginPass'
]);
$response->assertRedirect(action('PostController@index'));
}
/** @test */
public function 登録したのと違うメールアドレスでログインしようとしてもログインできない()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginPass')
]);
// ログイン処理
$response = $this->post(route('login'), [
'email' => 'pass@exae.com',
'password' => 'loginPass'
]);
$this->assertGuest();
}
/** @test */
public function 登録したのと違うパスワードでログインしようとしてもログインできない()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginpass')
]);
// ログイン処理
$response = $this->post(route('login'), [
'email' => 'pass@example.com',
'password' => 'liginpass'
]);
$this->assertGuest();
}
/** @test */
public function 異なるアドレスでログインしようとするとエラーメッセージが表示される()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginPass')
]);
$response = $this->post(route('login'), [
'email' => 'pass@exae.com',
'password' => 'loginPass'
]);
$errorMessage = 'メールアドレスまたはパスワードが間違っています';
$this->get(route('login'))->assertSee($errorMessage);
}
/** @test */
public function 異なるパスワードでログインしようとするとエラーメッセージが表示される()
{
$user = factory(User::class)->create([
'email' => 'pass@example.com',
'password' => bcrypt('loginpass')
]);
$response = $this->post(route('login'), [
'email' => 'pass@example.com',
'password' => 'lss'
]);
$errorMessage = 'メールアドレスまたはパスワードが間違っています';
$this->get(route('login'))->assertSee($errorMessage);
}
/** @test */
public function ログアウトすると認証状態が解除される()
{
$user = factory(User::class)->create();
$this->actingAs($user);
// ログアウトリクエスト
$response = $this->post(route('logout'));
// ユーザーが認証されていない
$this->assertGuest();
}
/** @test */
public function ログアウトをすると投稿一覧画面を表示しようとする()
{
$user = factory(User::class)->create();
$this->actingAs($user);
// ログアウトリクエスト
$response = $this->post(route('logout'));
$response->assertRedirect(action('PostController@index'));
}
/** @test */
public function ログアウト後はログイン画面に遷移する()
{
$user = factory(User::class)->create();
$this->actingAs($user);
// ログアウトリクエスト
$this->post(route('logout'));
$response = $this->get(action('PostController@index'));
$response->assertRedirect(route('login'));
}
}
ログイン(ログアウト)時に考えられる様々なシチュエーション(ログインできるか、ログインできなかった場合に画面はどうなるかなど)でテストを行いました。
おわりに
今回は以上となります。
(未来の自分も含めて)少しでもLaravelのコントローラテストを書く際の参考になっていれば幸いです。
