JavaScriptアプリケーション、特にシングルページアプリケーションにおいてエンドツーエンドテストは常に課題です。Laravelは最近、その課題の打開案となる新しいテストライブラリー、Duskの5.4バージョンをリリースしました。
今回のDuskのリリースで、Laravelはユーザーにブラウザーテスト用の共通APIを提供するようになりました。デフォルトでChromeDriverが付属し、ほかのブラウザーのサポートが必要な場合はSeleniumを使用できます。ニーズに応えるために、今後もこの共通テストAPIをサポートされる予定です。
この記事では、新しいLaravel 5.4アプリケーションを紹介します。
インストール
composer require laravel/dusk
上の方法でComposerを経由して最新の安定バージョンのパッケージをインストールできます。
次に、アプリケーション内でDuskServiceProviderを登録する必要があります。いくつかの方法があります。
アプローチ1
config/app.phpファイルにproviders配列を含められます。
...
App\Providers\RouteServiceProvider::class,
Laravel\Dusk\DuskServiceProvider::class,
...
このアプローチの問題点は、すべての環境でDuskServiceProviderがアプリケーションに登録されてしまうことです。本番環境でDuskを使えるようにする必要はありません。次のアプローチで、この現象を回避します。
アプローチ2
特定の環境のAppServiceProviderクラスにDuskServiceProviderを登録します。
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\DuskServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
if ($this->app->environment('local', 'testing', 'staging')) {
$this->app->register(DuskServiceProvider::class);
}
}
}
下のようにして、インストールプロセスを完了します。
php artisan dusk:install
このコマンドでDuskは基本的なクラスとディレクトリを構築します。testsディレクトリを開くと、Duskのテストに必要なファイルが入ったBrowserディレクトリが確認できます。
最初のテスト
最初に、Laravelに用意されている認証機能を使用して認証のワークフローを作ります。
php artisan make:auth
最初のDuskテストを作成します。
php artisan dusk:make LoginTest
上のコマンドでBrowserディレクトリにLoginTestクラスを作成します。
class LoginTest extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function test_I_can_login_successfully()
{
$this->browse(function ($browser) {
$browser->visit('/login')
->type('email', '[email protected]')
->type('password', 'secret')
->press('Login')
->assertSee('You are logged in!');
});
}
}
上のテストケースでは、ユーザーがシステムに正常にログインし、ホームページでウェルカムメッセージを確認できるかどうかをテストしています。
注意:このテストは、データベースに実際のユーザーが存在する必要があります。デモンストレーションでは、すでにデータベースに認証情報を持つユーザーを作成しています。
Duskテストを実行します。
php artisan dusk
データベースに正しい認証情報を持つユーザーが存在する場合、次の出力が表示されます。
PHPUnit 5.7.6 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 4.71 seconds, Memory: 10.00MB
OK (2 tests, 2 assertions)
テストに失敗した場合
テストに失敗するとPHPUnitはエラーをスローします。なにが悪かったのかを理解する必要があるからです。
Duskには、このユースケースに対応するための特徴的なオプションがいくつかあります。
テストを意図的に失敗するために修正します。
public function test_I_can_login_successfully()
{
$this->browse(function ($browser) {
$browser->visit('/login')
->type('email', '[email protected]')
->type('password', 'secret')
->press('Login')
->assertSee('You are logged in!');
});
}
上のテストケースでは、データベースに存在しないユーザーでログインしようとしています。Duskテストを実行すると、次のような起こります。
注意深く見ていると、テストが失敗する直前にブラウザーが開くのが分かります。
Duskでは、エラーが発生したページのスクリーンショットを取得し、自動的にgit-ignoredでscreenshotsディレクトリに保存します。
エラー発生時のスクリーンショットを保存することでテストが失敗する理由が視覚的に分かります。例では、認証情報がデータベースのレコードと一致していないことが確認できます。Duskを使えば、視覚的なフィードバックによって問題をすばやく特定できるようになります。
AJAXコールのテスト
Duskは、最新のJavaScriptアプリケーションのエンドツーエンドブラウザーテストを目的としています。このようなアプリケーションでは、典型的なユースケースとして、AJAXリクエストのレスポンスが用意されているケースがあります。
Githubで公開しているデモアプリのCreate Task機能を使ってテストします。Githubからクローンしてフォローしてください。
AJAXリクエストを使って新しいタスクを作成したあと、タスクリストページにリダイレクトさせます。これは完璧なテストのユースケースです。
php artisan dusk:make CreateTaskTest
上のコマンドでBrowserディレクトリにCreateTaskTestクラスが作成されます。次にテストを作成します。
class CreateTaskTest extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function test_I_can_create_task_successfully()
{
$this->browse(function ($browser) {
$browser->visit('/tasks/create')
->type('title', 'My Task')
->press('Add Task')
->pause(5000)
->assertPathIs('/tasks');
});
}
}
上のテストでは、AJAXフォームを使って次のような新しいテストを追加しています。
- タイトルを入力する
- タスク追加ボタンをクリックする
- 5秒間待つ
- tasksページへリダイレクトする
Duskテストを実行し、結果を確認します。
見ての通り、テストは成功しました。
上のフローをテストするため、DuskのAPIメソッドwaitUntilMissingも使用できます。
<?php
namespace Tests\Browser;
use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class CreateTaskTest extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function test_I_can_create_task_successfully()
{
$this->browse(function ($browser) {
$browser->visit('/tasks/create')
->type('title', 'My Task')
->press('Add Task')
->waitUntilMissing('.btn-primary')
->assertPathIs('/tasks');
});
}
}
APIで利用可能なWait要素については、公式ドキュメントを参照してください。
より高度な例
今度の例では、アプリケーションにメニューアイテムがあり、その中のサポートEメールリンクをクリックすると、サポートメールを送信するためのフォームをポップアップで表示します。
以下のシナリオをテストするテストケースを作成します。
- ログインする
- サポートEメールが表示される
- サポートEメールをクリックする
- モーダルが開き、テキストボックスにユーザーのEメールIDが表示される
上のgifアニメーションでは、マウスを使用してUIを操作し、モーダルを開いています。Duskのテストケースで上のフローを再作成します。
最初に、新しいテストクラスを作成します。
php artisan dusk:make SupportEmailsTest
次にテストを書きます。
class SupportEmailsTest extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function test_I_can_open_modal_for_support_emails()
{
$this->browse(function ($browser) {
$user = factory(User::class)->create();
$browser->loginAs($user)
->visit('/tasks')
->clickLink('Support Email')
->whenAvailable('#modal-support', function ($modal) use($user) {
$modal->assertInputValue('#support-from', $user->email);
});
});
}
}
最後に、実行します。
php artisan dusk tests/Browser/SupportEmailsTest.php
テストが成功したことが分かります。
PHPUnit 5.7.13 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 3.63 seconds, Memory: 12.00MB
OK (1 test, 1 assertion)
そのほかの利用可能なアサーションについては、公式ドキュメントを参照してください。
ページ
Duskにはページという概念があります。再利用可能なテストクラスで、非常に使いでがあります。
ページを使用してCreateTaskTestをリファクタリングします。
最初に新しいページを作成します。
php artisan dusk:page CreateTaskPage
上のコマンドでPagesディレクトリに新しいクラスが作成されます。各メソッドを確かめて、Create Taskテストケースを最適に修正します。
public function url()
{
return '/tasks/create';
}
urlメソッドはページのURLを定義します。このページが呼び出されるたびにDuskによって定義したURLに遷移されます。
public function assert(Browser $browser)
{
$browser->assertPathIs($this->url());
}
assertはこのページのアサーションを定義します。CreateTaskPageが呼ばれるたびにassertメソッドで定義されたすべてのアサーションが実行されます。
上の例ではシンプルに、アクティブなページのURLが適切であると定義しています。
public function elements()
{
return [
'@addTask' => '.btn-primary',
];
}
elementsメソッドは、事前にセレクタを定義できます。セレクタに読みやすい名前を付けると、異なるテストケースで再利用できます。上の例では、Add Taskボタンのセレクタを定義しました。
CreateTaskTestクラスを修正し、セレクタを使用します。
class CreateTaskTest extends DuskTestCase
{
/**
* A Dusk test example.
*
* @return void
*/
public function test_I_can_create_task_successfully()
{
$this->browse(function ($browser) {
$user = factory(User::class)->create();
$browser->loginAs($user)
->visit(new CreateTaskPage)
->type('title', 'My Task')
->click('@addTask')
->waitUntilMissing('@addTask')
->assertPathIs('/tasks');
});
}
}
CreateTaskPageを使ってクラスを修正しました。もう一度、テストして成功するか確認します。
PHPUnit 5.7.13 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 2.76 seconds, Memory: 12.00MB
OK (1 test, 2 assertions)
成功しました!
特定のページで、再利用可能なアクションを実行するカスタムメソッドも定義できます。詳しくは公式ドキュメントを参照してください。
最後に
この記事では、エンドツーエンドのJavaScriptテストツールLaravel Duskを取り上げました。使い始めるにあたって必要なオプションを例を使ってを解説し、ほかに使用可能なオプションについての概要も紹介しました。
※本記事はYounes Rafieが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:Laravel Dusk – Intuitive and Easy Browser Testing for All!)
[翻訳:萩原伸悟/編集:Livit]