イントロダクション
Laravelはユニットテストも考慮して構築されています。実際、PHPUnitをサポートしており、最初から含まれています。アプリケーションのためにphpunit.xml
ファイルも最初から準備されています。さらにフレームワークはアプリケーションを記述的にテストするために便利なヘルパメソッドも持っています。
ExampleTest.php
ファイルがtests
ディレクトリに提供されています。新しいLaravelアプリケーションをインストールした後、そのままphpunit
をコマンドラインで実行し試してみてください。
テスト動作環境
テスト実行時にLaravelは、設定環境を自動的にtesting
へセットします。そしてLaravelはセッションととキャッシュの設定ファイルをテスト環境で呼び出します。両方のドライバはテスト環境ではarray
にセットされます。つまりデータはテストを実行している間のみ存在しているということです。
必要であれば他のテスト設定環境を自由に作成することもできます。testing
動作環境変数はphpunit.xml
の中で設定されています。テスト実行前には、config:clear
Artisanコマンドを実行し、設定キャッシュをクリアするのを忘れないでください。
テストの定義と実行
新しいテストケースを作成するには、make:test
Artisanコマンドを使います。
php artisan make:test UserTest
上記のコマンドにより、tests
ディレクトリ中に新しいUserTest
クラスが設置されます。それから、いつもの通りPHPUnitを使ってテストメソッドを定義してください。テストを実行するには、ただターミナルでphpunit
コマンドを実行するだけです。
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UserTest extends TestCase
{
/**
* 基本的なテスト例
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}
注意:
setUp
メソッドを定義する場合はparent::setUp
を確実に呼び出してください。
アプリケーションのテスト
Laravelはアプリケーションに送るHTTPリクエストの作成、出力の検査、さらにフォームの入力もスラスラと書けるAPIを提供しています。例としてtests
ディレクトリに含まれるExampleTest.php
ファイルを見てください。
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
visit
メソッドはアプリケーションに対するGET
リクエストを作成します。see
メソッドは指定されたテキストがアプリケーションから返されるレスポンスの中に存在することをアサートします。dontSee
メソッドは指定したテキストがアプリケーションレスポンスの中へ返されていないことをアサートします。これはLaravelで使用できる最も基本的なアプリケーションテストです。
アプリケーションとの関わり
もちろんシンプルに指定したレスポンスの中にテキストが現れるかアサートする以上のことが可能です。リンクのクリックとフォームの埋め込みを見てみましょう。
リンクのクリック
このテストではアプリケーションへのリクエストを作成し、帰ってきたレスポンス中のリンクを「クリック」、それから指定されたURIに到達することをアサートします。たとえば"About Us"のテキスト値を持つレスポンスの中のリンクがあると仮定しましょう。
<a href="/about-us">About Us</a>
では、このリンクをクリックし、正しいページに到達したことをアサートしましょう。
public function testBasicExample()
{
$this->visit('/')
->click('About Us')
->seePageIs('/about-us');
}
フォームの埋め込み
Laravelはさらにフォームをテストするために多くのメソッドを提供しています。type
、select
、check
、attach
、press
メソッドはフォーム入力の全てを操作させてくれます。たとえば、アプリケーションの登録ページにこのフォームがあると考えてください。
<form action="/register" method="POST">
{{ csrf_field() }}
<div>
Name: <input type="text" name="name">
</div>
<div>
<input type="checkbox" value="yes" name="terms"> Accept Terms
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
このフォームを完全に埋め、結果を確認するテストが書けます。
public function testNewUserRegistration()
{
$this->visit('/register')
->type('Taylor', 'name')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
もちろん、フォームにラジオボタンやドロップボックスのような他の入力が含まれていても、同様にそうしたフィールドを簡単に埋めることができます。フォーム操作メソッドのリストを見てください。
メソッド | 説明 |
---|---|
$this->type($text, $elementName) |
指定したフィールドに「タイプ」します。 |
$this->select($value, $elementName) |
ラジオボタンかドロップダウンフィールドを「選択」します。 |
$this->check($elementName) |
チェックボックスフィールドを「チェック」します。 |
$this->uncheck($elementName) |
"Uncheck" a checkbox field. |
$this->attach($pathToFile, $elementName) |
フォームにファイルを「添付」します。 |
$this->press($buttonTextOrElementName) |
指定したテキストか名前のボタンを「押し」ます。 |
ファイル添付操作
フォームにfile
入力タイプが含まれているならば、attach
メソッドを使いフォームにファイルを添付できます。
public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->type('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
JSON APIのテスト
さらにLaravelはJSON
APIとそれらのレスポンスをテストするために、多くのヘルパも用意しています。たとえば、get
、post
、put
、patch
、delete
メソッドは各HTTP動詞でのリクエストを発行するために使用します。また簡単にデータとヘッダをメソッドに渡すこともできます。手始めに、/user
に対するPOST
リクエストを作成し、JSONフォーマットで指定指定した配列が返ってくることをアサートするテストを書いてみましょう。
<?php
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
seeJson
メソッドは渡された配列をJSONに変換します。次にそのJSONが、JSONレスポンス全体のいずれかに現れるかを確認します。ですから、他のプロパティーがJSONレスポンスに含まれていたとしても、指定した部分が存在する限り、テストはパスします。
JSONと一致するか厳格に検証
指定した配列がアプリケーションから返されるJSONと厳格に一致するかを確認したい場合は、seeJsonEquals
メソッドを使用してください。
<?php
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
JSON構造の合致
それには、seeJsonStructure
メソッドを使い、ネストしたキーのリストを渡します。
<?php
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->get('/user/1')
->seeJsonStructure([
'name',
'pet' => [
'name', 'age'
]
]);
}
}
上の例ではname
および、name
とage
を持つネストしたpet
オブジェクトを受け取ることを期待しています。seeJsonStructure
は、レスポンスが余計なキーを含んていても失敗しません。たとえば、pet
がweight
属性を持っていても、テストをパスします。
*
を使い、戻ってきたJSON構造がリストを持っていること、そして各リストが最低でも持っている属性を指定することもできます。
<?php
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
// 各ユーザはid、name、email属性を最低でも持っていることを宣言
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email'
]
]);
}
}
*
記述法はネストもできます。次の例の場合、JSONレスポンスの各ユーザは指定されている属性を持っており、各ユーザの各ペットも指定されている属性を持っていることを宣言します。
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email', 'pets' => [
'*' => [
'name', 'age'
]
]
]
]);
セッション/認証
Laravelはテスト時にセッションを操作するたくさんのヘルパも提供しています。1つ目は指定した配列をセッションに保存するwithSession
メソッドです。これはアプリケーションのリクエストをテストする前に、データをセッションにロードしたい場合に便利です。
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
もちろん認証済みのユーザのようなユーザ状態をセッションへ保持するのは一般的です。actingAs
ヘルパメソッドは現在認証済みのユーザを指定する簡単な手段を提供します。たとえば、モデルファクトリでユーザを生成し、認証してみましょう。
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$user = factory(App\User::class)->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
ユーザの認証にどのガードを使用するかを指定したい場合、actingAs
メソッドの第2引数にガード名を渡します。
$this->actingAs($user, 'backend')
ミドルウェアの無効化
アプリケーションをテストするとき、いくつかのテストではミドルウェアを無効にするほうが便利だと気がつくでしょう。これによりミドルウェアに関わらずにルートやコントローラーのテストが可能になります。LaravelはシンプルなWithoutMiddleware
トレイトを用意しており、これを使えば自動的にテストクラスのミドルウェアを無効にできます。
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use WithoutMiddleware;
//
}
いくつかのテストメソッドだけでミドルウェアを無効にしたい場合は、withoutMiddleware
メソッドをテストメソッドの中で呼び出してください。
<?php
class ExampleTest extends TestCase
{
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
カスタムHTTPリクエスト
アプリケーションに対してカスタムHTTPリクエストを作成し、完全なIlluminate\Http\Response
オブジェクトを取得したい場合は、call
メソッドを使ってください。
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
POST
やPUT
、PATCH
リクエストの場合、そのリクエストに入力データの配列を渡すことができます。もちろん、このデーターはRequestインスタンスを使用し、ルートやコントローラーで使用できます。
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
PHPUnitのアサート
PHPUnitテスト用に、数多くの追加アサートメソッドをLaravelは提供しています。
メソッド | 説明 |
---|---|
->assertResponseOk(); |
クライアントのレスポンスがOKのステータスコードを受け取ることを宣言 |
->assertResponseStatus($code); |
クライアントのレスポンスが指定したコードであることを宣言 |
->assertViewHas($key, $value = null); |
レスポンスのビューに指定したデータが含まれていることを宣言 |
->assertViewHasAll(array $bindings); |
ビューが指定したデータのリストを持っていることを宣言 |
->assertViewMissing($key); |
ビューが指定したデータを含んでいないことを宣言 |
->assertRedirectedTo($uri, $with = []); |
クライアントが指定したURIへリダイレクトすることを宣言 |
->assertRedirectedToRoute($name, $parameters = [], $with = []); |
クライアントが指定したルートへリダイレクトされることを宣言 |
->assertRedirectedToAction($name, $parameters = [], $with = []); |
クライアントが指定したアクションへリダイレクトすることを宣言 |
->assertSessionHas($key, $value = null); |
セッションが指定した値を持つことを宣言 |
->assertSessionHasAll(array $bindings); |
セッションが指定したリストを持つことを宣言 |
->assertSessionHasErrors($bindings = [], $format = null); |
セッションにエラー情報があることを宣言 |
->assertHasOldInput(); |
セッションが直前の入力を持つことを宣言 |
->assertSessionMissing($key); |
セッションが指定したキーを持っていないことを宣言 |
データベースとの関わり
Laravelではさらに、データベースを駆動するアプリケーションのテストを簡単にできる多くの便利なツールを用意しています。その一つは指定した抽出条件に一致するデータがデータベース中に存在するかをアサートするseeInDatabase
ヘルパです。たとえばuser
テーブルの中にemail
フィールドがsally@example.com
の値のレコードが存在するかを確認したいとしましょう。次のようにテストできます。
public function testDatabase()
{
// アプリケーションを呼び出す…
$this->seeInDatabase('users', ['email' => 'sally@example.com']);
}
もちろん、seeInDatabase
メソッドやその他のヘルパは、利便性のために用意しています。PHPUnitの組み込みアサートメソッドを自分のテストに使用するのを制限するものではありません。
テストごとのデータベースリセット
直前のテストのデータが以降のテストに影響を与えないようにするために、それぞれのテストの後にデータベースをリセットできれば便利です。
マイグレーションの使用
一つの選択肢はそれぞれのテストの後にデータベースをロールバックし、次のテストの前にマイグレーションする方法です。Laravelはこれを自動的に処理するために、シンプルなDatabaseMigrations
トレイトを用意しています。テストクラスでこのトレイトを使用するだけです。
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
トランザクションの使用
もう一つの選択肢は、各テストケースをデータベーストランザクションでラップしてしまうことです。Laravelはこれを自動的に行うために便利なDatabaseTransactions
トレイトを用意してます。
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseTransactions;
/**
* 基本的な機能テストの例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
注意: このトレイトはデフォルトのデータベース接続をトランザクションでラップしているだけです。
モデルファクトリ
テスト実行前に何件かのレコードをデータベースに挿入する必要はよく起きます。こうしたテストデータを手動でそれぞれのカラムへ値を指定する代わりに、Laravelでは「ファクトリ」を使用しEloquentモデルの各属性にデフォルトを設定できます。手始めにアプリケーションのdatabase/factories/ModelFactory.php
ファイルを見てください。このファイルには最初からファクトリの定義が含まれています。
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
ファクトリで定義しているクロージャの中から、モデルの全属性に対するデフォルトのテスト値を返しています。このクロージャはFaker PHPライブラリーのインスタンスを受け取っており、これはテストのための多用なランダムデータを生成するのに便利です。
もちろん追加のファクトリをModelFactory.php
に自由に追加できます。系統立てるためには各モデルのファクトリーファイルを追加したほうが良いでしょう。たとえば、database/factories
ディレクトリの中へUserFactory.php
とCommentFactory.php
ファイルを作成します。
複数のファクトリタイプ
時々、同じEloquentモデルクラスに対し、複数のファクトリを持ちたいことがあります。たとえば、たぶん通常のユーザに加えて「管理者(administrator)」のためのファクトリも用意したいでしょう。defineAs
メソッドを使用すれば、そうしたファクトリを定義できます。
$factory->defineAs(App\User::class, 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => str_random(10),
'remember_token' => str_random(10),
'admin' => true,
];
});
基本のユーザファクトリから全属性を複製する代わりに、属性を取得するraw
メソッドを使用できます。一度属性を取得したら、必要な追加の値を追加できます。
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
$user = $factory->raw(App\User::class);
return array_merge($user, ['admin' => true]);
});
テスト中のファクトリ使用
ファクトリを定義し終えたら、テストかデータベースのシーディング(初期値設定)ファイルの中で、グローバルなfactory
関数を使用してモデルインスタンスを生成できます。では、モデル生成の例をいくつか見てみましょう。最初はmake
メソッドでモデルを生成し、データベースには保存しないでみましょう。
public function testDatabase()
{
$user = factory(App\User::class)->make();
// モデルをテストで使用…
}
モデルのデフォルト値をオーバーライドしたい場合は、make
メソッドに配列で値を渡してください。指定した値のみ置き換わり、残りの値はファクトリで指定したデフォルト値のまま残ります。
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
さらにモデルのコレクションや指定したタイプのモデルも生成できます。
// App\Userインスタンスを3つ生成
$users = factory(App\User::class, 3)->make();
// App\Userの"admin"インスタンスを生成
$user = factory(App\User::class, 'admin')->make();
// App\Userの"admin"インスタンスを3つ生成
$users = factory(App\User::class, 'admin', 3)->make();
保存するファクトリモデル
create
メソッドはモデルインスタンスを生成するだけでなく、Eloquentのsave
メソッドを使用しデータベースへ保存します。
public function testDatabase()
{
$user = factory(App\User::class)->create();
// テスト中で生成したモデルを使用する…
}
ここでも、create
メソッドに配列で値を渡すことで、モデルの属性をオーバーライドできます。
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
モデルへのリレーション追加
複数のモデルをデータベースへ保存することもできます。以下の例では、生成したモデルにリレーションを付けています。複数モデルの生成にcreate
メソッドを使用する場合、インスタンスのコレクションが返されます。そのため、コレクションで使用できるeach
などの便利な関数が利用できます。
$users = factory(App\User::class, 3)
->create()
->each(function ($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
リレーションと属性クロージャ
クロージャ属性をファクトリ定義の中で使い、モデルとのリレーションを追加することもできます。たとえば、Post
を作成する時に、新しいUser
インスタンスも作成したい場合は、以下のようになります。
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
}
];
});
さらに、クロージャは評価済みのファクトリーの配列を受け取ることもできます。
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'user_type' => function (array $post) {
return App\User::find($post['user_id'])->type;
}
];
});
モック
イベントのモック
Laravelのイベントシステムを多用している場合、テスト中は静かにしてもらうか、特定のイベントモックしたいと思うでしょう。たとえばユーザ登録のテスト中は、たぶんUserRegistered
イベントのハンドラには全部起動してもらいたくないでしょう。起動されたら"Welcome"メールを送り…などされるでしょう。
Laravelはイベントハンドラを実行させずに期待するイベントが発行されたことを確認する、便利なexpectsEvents
メソッドを用意しています。
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// ユーザー登録コードのテスト…
}
}
指定したイベントが発行されないことを確認するには、doesntExpectEvents
メソッドを使います。
<?php
class ExampleTest extends TestCase
{
public function testPodcastPurchase()
{
$this->expectsEvents(App\Events\PodcastWasPurchased::class);
$this->doesntExpectEvents(App\Events\PaymentWasDeclined::class);
// ポッドキャスト購入コードのテスト…
}
}
全イベントハンドラの実行を停止したい場合は、withoutEvents
メソッドを使用してください。
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->withoutEvents();
// ユーザー登録コードのテスト…
}
}
ジョブのモック
場合によりアプリケーションへリクエストを作成し、コントローラーにより特定のジョブがディスパッチされることだけをテストしたい場合もあります。これにより、ジョブのロジックから切り離し、ルート/コントローラーを独立してテストできるようになります。もちろん、それからジョブ自身を別のテストクラスに分けてテストできます。
Laravelは便利なexpectsJobs
メソッドを用意しており、期待しているジョブがディスパッチされたかを検査できます。しかしジョブ自身は実行されません。
<?php
class ExampleTest extends TestCase
{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// ポッドキャスト購入コードのテスト…
}
}
注意: このメソッドは
DispatchesJobs
トレイトのdispatch
メソッドか、dispatch
ヘルパ関数により起動されたジョブだけを検知します。直接Queue::push
で送られたジョブは検知できません。
ファサードのモック
テスト時、頻繁にLaravelのファサードの呼び出しをモックしたくなります。たとえば以下のようなコントローラーアクションを考えてください。
<?php
namespace App\Http\Controllers;
use Cache;
class UserController extends Controller
{
/**
* アプリケーションの全ユーザーリストの表示
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
shouldReceive
メソッドを使用し、Cache
ファサードへの呼び出しをモックできます。これはMockeryのモックインスタンスを返します。ファサードはLaravelのサービスコンテナにより管理され、依存解決されていますので、典型的な静的クラスよりもかなり高いテスタビリティーを持っています。例としてCache
ファサードへの呼び出しをモックしてみましょう。
<?php
class FooTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
注意:
Request
ファサードはモックしてはいけません。テスト実行時にcall
とpost
のようなHTTPヘルパメソッドへ望みの入力を渡してください。