イントロダクションIntroduction
Laravelアプリケーションをテストするとき、アプリケーションの一部分を「モック」し、特定のテストを行う間は実際のコードを実行したくない場合があります。たとえば、イベントをディスパッチするコントローラをテストする場合、テスト中に実際に実行されないように、イベントリスナをモックすることができます。これにより、イベントリスナはそれ自身のテストケースでテストできるため、イベントリスナの実行について気を取られずに、コントローラのHTTPレスポンスのみをテストできます。When testing Laravel applications, you may wish to "mock" certain aspects of your application so they are not actually executed during a given test. For example, when testing a controller that dispatches an event, you may wish to mock the event listeners so they are not actually executed during the test. This allows you to only test the controller's HTTP response without worrying about the execution of the event listeners since the event listeners can be tested in their own test case.
Laravelは最初からイベント、ジョブ、その他のファサードをモックするための便利な方法を提供しています。これらのヘルパは主にMockeryの便利なレイヤーを提供するため、複雑なMockeryメソッド呼び出しを手作業で行う必要はありません。Laravel provides helpful methods for mocking events, jobs, and other facades out of the box. These helpers primarily provide a convenience layer over Mockery so you do not have to manually make complicated Mockery method calls.
オブジェクトのモックMocking Objects
Laravelのサービスコンテナを介してアプリケーションに注入されるオブジェクトをモックする場合、モックしたインスタンスをinstance
結合としてコンテナに結合する必要があります。これにより、オブジェクト自体を構築する代わりに、オブジェクトのモックインスタンスを使用するようコンテナへ指示できます。When mocking an object that is going to be injected into your application via Laravel's service container[/docs/{{version}}/container], you will need to bind your mocked instance into the container as an instance
binding. This will instruct the container to use your mocked instance of the object instead of constructing the object itself:
use App\Service;
use Mockery;
use Mockery\MockInterface;
public function test_something_can_be_mocked(): void
{
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
})
);
}
これをより便利にするために、Laravelの基本テストケースクラスが提供するmock
メソッドを使用できます。たとえば、以下の例は上記の例と同じです。In order to make this more convenient, you may use the mock
method that is provided by Laravel's base test case class. For example, the following example is equivalent to the example above:
use App\Service;
use Mockery\MockInterface;
$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
オブジェクトのいくつかのメソッドをモックするだけでよい場合は、partialMock
メソッドを使用できます。モックされていないメソッドは、通常どおり呼び出されたときに実行されます。You may use the partialMock
method when you only need to mock a few methods of an object. The methods that are not mocked will be executed normally when called:
use App\Service;
use Mockery\MockInterface;
$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
同様に、オブジェクトをスパイしたい場合のため、Laravelの基本テストケースクラスは、Mockery::spy
メソッドの便利なラッパーとしてspy
メソッドを提供しています。スパイはモックに似ています。ただし、スパイはスパイとテスト対象のコードとの間のやり取りを一度記録するため、コードの実行後にアサーションを作成できます。Similarly, if you want to spy[http://docs.mockery.io/en/latest/reference/spies.html] on an object, Laravel's base test case class offers a spy
method as a convenient wrapper around the Mockery::spy
method. Spies are similar to mocks; however, spies record any interaction between the spy and the code being tested, allowing you to make assertions after the code is executed:
use App\Service;
$spy = $this->spy(Service::class);
// …
$spy->shouldHaveReceived('process');
ファサードのモックMocking Facades
従来の静的メソッド呼び出しとは異なり、ファサード(リアルタイムファサードを含む)もモックできます。これにより、従来の静的メソッドに比べて大きな利点が得られ、従来の依存注入を使用した場合と同じくテストが簡単になります。テスト時、コントローラの1つで発生するLaravelファサードへの呼び出しをモックしたい場合がよくあるでしょう。例として、次のコントローラアクションについて考えてみます。Unlike traditional static method calls, facades[/docs/{{version}}/facades] (including real-time facades[/docs/{{version}}/facades#real-time-facades]) may be mocked. This provides a great advantage over traditional static methods and grants you the same testability that you would have if you were using traditional dependency injection. When testing, you may often want to mock a call to a Laravel facade that occurs in one of your controllers. For example, consider the following controller action:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* アプリケーションのすべてのユーザーのリストを取得
*/
public function index(): array
{
$value = Cache::get('key');
return [
// ...
];
}
}
shouldReceive
メソッドを使用してCache
ファサードへの呼び出しをモックできます。これにより、Mockeryモックのインスタンスが返されます。ファサードは実際にはLaravelサービスコンテナによって依存解決および管理されるため、通常の静的クラスよりもはるかにテストがやりやすいのです。たとえば、Cache
ファサードのget
メソッドの呼び出しをモックしてみましょう。We can mock the call to the Cache
facade by using the shouldReceive
method, which will return an instance of a Mockery[https://github.com/padraic/mockery] mock. Since facades are actually resolved and managed by the Laravel service container[/docs/{{version}}/container], they have much more testability than a typical static class. For example, let's mock our call to the Cache
facade's get
method:
<?php
namespace Tests\Feature;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class UserControllerTest extends TestCase
{
public function test_get_index(): void
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$response = $this->get('/users');
// …
}
}
Warning!
Request
ファサードをモックしないでください。代わりに、テストの実行時に、get
やpost
などのHTTPテストメソッドに必要な入力を渡します。同様に、Config
ファサードをモックする代わりに、テストではConfig::set
メソッドを呼び出してください。[!WARNING]
You should not mock theRequest
facade. Instead, pass the input you desire into the HTTP testing methods[/docs/{{version}}/http-tests] such asget
andpost
when running your test. Likewise, instead of mocking theConfig
facade, call theConfig::set
method in your tests.
ファサードのスパイFacade Spies
ファサードでスパイしたい場合は、対応するファサードでspy
メソッドを呼び出します。スパイはモックに似ています。ただし、スパイはスパイとテスト対象のコードとの間のやり取りを一時的に記録しているため、コードの実行後にアサーションを作成できます。If you would like to spy[http://docs.mockery.io/en/latest/reference/spies.html] on a facade, you may call the spy
method on the corresponding facade. Spies are similar to mocks; however, spies record any interaction between the spy and the code being tested, allowing you to make assertions after the code is executed:
use Illuminate\Support\Facades\Cache;
public function test_values_are_be_stored_in_cache(): void
{
Cache::spy();
$response = $this->get('/');
$response->assertStatus(200);
Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
}
時間操作Interacting With Time
テスト時、now
やIlluminate\Support\Carbon::now()
のようなヘルパが返す時間を変更したいことはよくあります。幸いなことに、Laravelのベース機能テストクラスは現在時間を操作するヘルパを用意しています。When testing, you may occasionally need to modify the time returned by helpers such as now
or Illuminate\Support\Carbon::now()
. Thankfully, Laravel's base feature test class includes helpers that allow you to manipulate the current time:
use Illuminate\Support\Carbon;
public function test_time_can_be_manipulated(): void
{
// 未来へ移行する
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// 過去へ移行する
$this->travel(-5)->hours();
// 特定の時間へ移行する
$this->travelTo(now()->subHours(6));
// 現在時刻へ戻る
$this->travelBack();
}
また、さまざまな時間移動のメソッドへ、クロージャを提供することもできます。クロージャは、指定された時刻で時間を止めたまま起動します。クロージャが実行されると、時間は通常通り再開されます。You may also provide a closure to the various time travel methods. The closure will be invoked with time frozen at the specified time. Once the closure has executed, time will resume as normal:
$this->travel(5)->days(function () {
// 5日後の将来で、何かをテストする…
});
$this->travelTo(now()->subDays(10), function () {
// 指定した時間で、何かをテストする…
});
freezeTime
メソッドは、現在の時刻を停止するために使用します。同様に、freezeSecond
メソッドは、現在の時刻で止めますが、現在秒の先頭で止めます。The freezeTime
method may be used to freeze the current time. Similarly, the freezeSecond
method will freeze the current time but at the start of the current second:
use Illuminate\Support\Carbon;
// 時刻を止め、クロージャ実行後は通常どおりに再開する
$this->freezeTime(function (Carbon $time) {
// ...
});
// 現在秒で時刻を止め、クロージャ実行後は通常通りに再開する
$this->freezeSecond(function (Carbon $time) {
// ...
})
ご想像の通り、上記の方法はすべて、ディスカッションフォーラムで非アクティブな投稿をロックするなど、時間に敏感なアプリケーションの動作をテストするのに主に役立ちます。As you would expect, all of the methods discussed above are primarily useful for testing time sensitive application behavior, such as locking inactive posts on a discussion forum:
use App\Models\Thread;
public function test_forum_threads_lock_after_one_week_of_inactivity()
{
$thread = Thread::factory()->create();
$this->travel(1)->week();
$this->assertTrue($thread->isLockedByInactivity());
}