ログイン、投稿、編集、削除のブラウザテストを記述し、実行します。
親記事
Laravel 5.7で基本的なCRUDを作る - Qiita
Dusk用のDBと環境ファイルを用意する
前のテストがその後のテストデータに影響しないように、各テストの後にデータベースをリセットできると便利です。インメモリデータベースを使っていても、トラディショナルなデータベースを使用していても、RefreshDatabaseトレイトにより、マイグレーションに最適なアプローチが取れます。テストクラスてこのトレイトを使うだけで、全てが処理されます。
readouble.com: 各テスト後のデータベースリセット
リセットしても普段のローカル環境に影響が及ばないように、Dusk用のDBと環境ファイルを用意します。
Laravel 5.4で導入されたLaravel Duskをテスト後にDBリセットさせるようにして試してみた - Qiita
Dusk用のDBもローカルと同じくMySQLを使うことにします。
- データベース名: laravel57-dusk
- 照合順序(Collation): utf8mb4_unicode_ci
# ユーザー名「root」、パスワードなしで接続
> mysql --user=root --password=
# 長いので改行しています。
MariaDB [(none)]> CREATE DATABASE `laravel57-dusk`
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Dusk用の環境ファイルを作成します。
.env.dusk.local
と名付ければ、ローカルでのDusk実行時に自動で選ばれます。
readouble.com: 環境の処理
ファイルの内容は下記のようにします。
.env.dusk.local
【重要】 .envのコピー
公式ドキュメントにあるとおり、ブラウザテストを実行すると.env
ファイルの内容が一時的に書き換わります。
具体的には下記の流れをたどるようです。
-
.env
のコピーが作られ、.env.backup
と命名される。 -
.env
の内容が.env.duck.local
の内容に置き換わる。 - ブラウザテスト終了。
-
.env
の内容が.env.backup
に置き換わり、元に戻る。 -
.env.backup
が削除される。
しかし、テスト中にエラーが有ると、.env
の内容が.env.dusk.local
の内容のままで元に戻らないことがあります。
一方、.env.backup
は削除されます。
そうなると、もともとの.env
の内容は完全に消えてしまいます。
.env
はGitで管理されないので、復元不可能です。
そのため、私はあらかじめ.env
をコピーして.env.mycopy
と命名して保存しています。
当然、.env.mycopy
もGitで管理すべきではないので、設定を追加します。
# Duskのテストでエラーがあると .env が壊れる場合があるため
# 設定ファイルを手動でコピーして保存しておく
.env.mycopy
Laravel5.4ではFakerが必要
(Laravel5.5以降は最初からFakerが依存パッケージに指定されているので、下記の操作は不要です)
以下のブラウザテストでは、ファクトリを使ってテスト用にDBのレコードを生成しています。
そのファクトリの内部ではFakerを利用しているので、あらかじめインストールしておいてください。
Fakerがないとテスト実行時に下記のようなエラーが表示されます。
Error: Class 'Faker\Factory' not found
# Fakerをインストール
> composer require-dev fzaninotto/faker
# 私は、本番環境のHerokuでも使いたいので「require」でインストールしました。
> composer require fzaninotto/faker
ブラウザのサイズを指定する
以下のテストでは、グローバルナビ内の「投稿する」ボタンをクリックするという動作を指示しています。
しかし、ブラウザの幅が狭いとレスポンシブ・デザインによってグローバルナビ内のメニューが隠れてしまいます。
こうなると「ボタンが存在しない!」とテストに判断されてエラーとなってしまいます。
そこで、テスト用のブラウザのサイズを指定して、PC向けの広い画面でテストすることを明示します。
Bootstrap4のブレークポイントの基準で「Large devices(992px以上)」となるように横幅を指定します。
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
- '--headless'
+ '--headless',
+ // Bootstrap4のブレークポイントの基準で「Large devices(992px以上)」となるよう横幅を指定。
+ // ただし、ウィンドウ枠などを考慮して余裕をもたせる。
+ '--window-size=1100,600',
]);
ログインのテスト
readouble.com: テストの生成
# テストファイルを生成
> php artisan dusk:make LoginTest
ファイルの中身を下記のようにします。
tests/Browser/LoginTest.php
# テストを実行する
> php artisan dusk --filter 'LoginTest'
テストでは英語に固定する
上のテストコードでは、ログインボタンをクリックする際はpress('Login')
のようにボタンのテキストを指定しています。
テキストよりもname属性を指定するのが無難ですが、Auth関連で自動生成されたビューでは<button type="submit" class="btn btn-primary">
のようにname属性が記載されていません。
ヘッドレスブラウザではロケールは英語となるようですが、前回の記事のように'--headless'
を無効にして実際にブラウザを起動させると日本語となる場合があり、そうなるとボタンのテキストはログイン
となります。
これではDuskがボタンを探し出せずにエラーとなってしまいます。
なお、Laracastsでの質問によると、テストコード内では__()
などのLaravelの関数は使えないようです。
press
メソッドの引数には、ボタンのテキスト、value属性、id属性、name属性の他に、CSSセレクタも受け付けています。
DuskでHTML要素の探索を担当しているソースコード: ElementResolver
よって、press('[type="submit"]')
とすれば日本語にも英語にも対応できます。
今回はこれで解決できますが、特定のメッセージが表示されたかどうかをテストしたい場合もあるでしょう。
そのメッセージがロケールによって異なる場合、テストコードを書く手立てがありません。
そこで、環境がtesting
の場合は英語に固定するように、ミドルウェアのhandle
メソッドの冒頭に追記します。
Stack Overflow: php - Forcing locale in Laravel Dusk
これで、多言語対応している場合でもページ内の文章に依存したテストを書けるようになります。
public function handle($request, Closure $next)
{
// テストでは使用言語を英語で固定する
// Dusk内では __() などは使えないため
// 参照: https://laracasts.com/discuss/channels/testing/get-translated-text-in-waitfortext-dusk-dusk-dusk
if (\App::environment('testing')) {
\App::setLocale('en');
return $next($request);
}
CRUDのテスト
User用のファクトリを修正する
Userモデルのファクトリはデフォルトで用意されています。
なお、はじめから用意されているファクトリのファイル名は、Laravel5.4ではModelFactory.php
でしたが、5.5以降はUserFactory.php
です。
「1つのファイルには1つのモデルを」という方針を徹底すべきようなので、それに従うことにします。
以前の記事でメール確認を有効にしているので、下記のようにemail_verified_at
カラムに日付を挿入しなければ、たとえテストであっても記事を投稿することができません。
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
+ 'email_verified_at' => $faker->dateTime(),
'remember_token' => str_random(10),
];
Post用のファクトリを作る
Postモデルのファクトリは存在しないので、新たに生成します。
ファクトリを作る際、--model=Post
のようにしてモデル名を指定すると後が楽です。
readouble.com: ファクトリの生成
> php artisan make:factory PostFactory --model=Post
ファイルの中身は下記のようにして、題名と本文をランダムに作成します。
database/factories/PostFactory.php
テストを記述する
# テストファイルを生成
> php artisan dusk:make PostTest
ファイルの中身を下記のようにします。
tests/Browser/PostTest.php
ファクトリを利用する時はcreate
とmake
の違いに注意してください。
create
はデータを自動生成して、さらにデータベースに保存します。
make
は自動生成するだけです。
モデルの保存
モデルの生成
記事の削除では、削除確認のモーダルが表示されるまで待つためにwhenAvailable
メソッドを使います。
利用可能時限定のセレクタ
# テストを実行する
> php artisan dusk --filter 'PostTest'
ポリシーのテスト
# テストファイルを生成
> php artisan dusk:make PolicyTest
ファイルの中身を下記のようにします。
tests/Browser/PolicyTest.php
# テストを実行する
> php artisan dusk --filter 'PolicyTest'
オーナーではない記事を普通のユーザーが編集しようとすると403エラーになることを確認します。
具体的には、403エラー用のビューに設置しておいたHTML要素<span class="error-code">403</span>
を手がかりにして、下記のように確認しています。
$browser->assertSeeIn('.error-code', '403');
Laravel本家にはassertStatusというアサーション・メソッドがありますが、Duskでは使えません。
本家でもDuskでも内部でPHPUnitのアサーション・メソッドを利用していますが、両者は全く独立していて、Duskが本家のメソッドを内包しているわけではありません。
Duskの$browser
のどこかでIlluminate\Http\Responseのインスタンスを取り出せないかと探してみましたが、ダメでした。
これではHTTPステータスコードを取得できません。
よって、上の方法しかありませんでした。
本家のアサーション・メソッドが定義されているファイル
Duskのアサーション・メソッドが定義されているファイル
注意: ブラウザテストのカバレッジは取得できない
> php artisan dusk --coverage-html cover-dusk
上のコマンドでcover-dusk/
フォルダ内にカバレッジの結果が一応生成されます。
しかし、結果は正常ではありません。
実行されたはずのコントローラのアクションのカバレッジが0%となっています。
PHPUnitの@coversアノテーションで明示してもダメです。
まあ、アクションのページにブラウザがアクセスしているというだけで、テストコード内ではアクションのメソッドを全く実行していないのだから仕方ありません…。
@coversNothingという、カバレッジの対象外とするためのアノテーションが存在し、その説明の中で「This can be used for integration testing. 」とあるくらいですから、結合テストやブラウザテストではカバレッジを取得できないのが当たり前なのでしょう。