Khi test các application của Laravel, bạn có thể muốn "làm giả" các khía cạnh nhất định của application để chúng không thực sự được thực thi trong khi test. Ví dụ: khi test một controller gửi một event, bạn có thể muốn làm giả một event listener để chúng không thực sự được thực thi trong quá trình test. Điều này cho phép bạn chỉ kiểm tra HTTP response của controller mà không phải lo lắng về việc thực thi của event listener, vì các event listener có thể được kiểm tra trong một test case của riêng nó.
Mặc định, Laravel cung cấp helper để làm giả các event, job và facade. Những helper này chủ yếu cung cấp một layer dựa trên Mockery để bạn không phải tự thực hiện các việc gọi phương thức Mockery phức tạp. Tuy nhiên, bạn cũng có thể sử dụng Mockery hoặc PHPUnit để làm giả hoặc spy của riêng bạn.
Khi giả một đối tượng sẽ được tích hợp vào ứng dụng của bạn thông qua service container của Laravel, bạn sẽ cần phải liên kết instance giả của bạn vào container dưới dạng liên kết instance
. Điều này sẽ hướng dẫn container sử dụng instance đối tượng được làm giả của bạn thay vì khởi tạo chính đối tượng đó:
use App\Service;
use Mockery;
$this->instance(Service::class, Mockery::mock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once();
}));
Để làm cho việc này thuận tiện hơn, bạn có thể sử dụng phương thức mock
, được cung cấp bởi class base test case của Laravel:
use App\Service;
$this->mock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once();
});
Bạn có thể sử dụng phương thức partialMock
khi bạn chỉ cần làm giả một vài phương thức của một đối tượng. Các phương thức không bị làm giả sẽ được thực thi bình thường khi được gọi:
use App\Service;
$this->partialMock(Service::class, function ($mock) {
$mock->shouldReceive('process')->once();
});
Tương tự, nếu bạn muốn theo dõi một đối tượng, class base test case của Laravel cũng cung cấp phương thức spy
là một phương thức bao bọc cho phương thức Mockery::spy
:
use App\Service;
$this->spy(Service::class, function ($mock) {
$mock->shouldHaveReceived('process');
});
Thay cho việc làm giả, bạn có thể sử dụng phương thức fake
của facade Bus
để ngăn job được gửi đi. Khi sử dụng fake, các assertion sẽ được tạo sau khi code test được thực thi:
<?php
namespace Tests\Feature;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Bus::fake();
// Perform order shipping...
// Assert a specific type of job was dispatched meeting the given truth test...
Bus::assertDispatched(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
// Assert a job was not dispatched...
Bus::assertNotDispatched(AnotherJob::class);
}
}
Thay cho việc làm giả, bạn có thể sử dụng phương thức fake
của facade Event
để ngăn tất cả những event listener sẽ được thực thi. Sau đó, bạn có thể xác nhận các event đã được gửi đi hay chưa và thậm chí kiểm tra dữ liệu mà nó nhận được. Khi sử dụng fake, các assertion sẽ được thực hiện sau khi code test được thực thi:
<?php
namespace Tests\Feature;
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* Test order shipping.
*/
public function testOrderShipping()
{
Event::fake();
// Perform order shipping...
// Assert a specific type of event was dispatched meeting the given truth test...
Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});
// Assert an event was dispatched twice...
Event::assertDispatched(OrderShipped::class, 2);
// Assert an event was not dispatched...
Event::assertNotDispatched(OrderFailedToShip::class);
}
}
{note} Sau khi bạn gọi
Event::fake()
, thì sẽ không có event listener nào được thực thi. Vì vậy, nếu các bài test của bạn đang sử dụng các model factory mà có dựa vào các event, chẳng hạn như tạo UUID trong eventcreating
của một model, thì bạn nên gọiEvent::fake()
sau khi sử dụng các factory đó của bạn.
Nếu bạn chỉ muốn làm giả event listener cho một nhóm event cụ thể, thì bạn có thể truyền chúng sang phương thức fake
hoặc fakeFor
:
/**
* Test order process.
*/
public function testOrderProcess()
{
Event::fake([
OrderCreated::class,
]);
$order = factory(Order::class)->create();
Event::assertDispatched(OrderCreated::class);
// Other events are dispatched as normal...
$order->update([...]);
}
Nếu bạn chỉ muốn làm giả một event listener trong một phần bài test của bạn, bạn có thể sử dụng phương thức fakeFor
:
<?php
namespace Tests\Feature;
use App\Events\OrderCreated;
use App\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* Test order process.
*/
public function testOrderProcess()
{
$order = Event::fakeFor(function () {
$order = factory(Order::class)->create();
Event::assertDispatched(OrderCreated::class);
return $order;
});
// Events are dispatched as normal and observers will run ...
$order->update([...]);
}
}
Phương thức fake
của facade Http
cho phép bạn hướng dẫn HTTP client trả về các stubbed hoặc dummy response khi một request được tạo. Để biết thêm thông tin chi tiết về việc faking các request HTTP được gửi đi, vui lòng tham khảo tài liệu testing HTTP Client.
Bạn có thể sử dụng phương thức fake
của facade Mail
để ngăn mail được gửi. Sau đó, bạn có thể xác nhận mailables đã được gửi đi cho người dùng hay chưa và thậm chí kiểm tra dữ liệu nó nhận được. Khi sử dụng fake, các assertion được thực hiện sau khi code test được thực thi:
<?php
namespace Tests\Feature;
use App\Mail\OrderShipped;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Mail::fake();
// Assert that no mailables were sent...
Mail::assertNothingSent();
// Perform order shipping...
// Assert a specific type of mailable was dispatched meeting the given truth test...
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});
// Assert a message was sent to the given users...
Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
return $mail->hasTo($user->email) &&
$mail->hasCc('...') &&
$mail->hasBcc('...');
});
// Assert a mailable was sent twice...
Mail::assertSent(OrderShipped::class, 2);
// Assert a mailable was not sent...
Mail::assertNotSent(AnotherMailable::class);
}
}
Nếu bạn đang sử dụng queue mailable để gửi ở dưới background, bạn nên sử dụng phương thức assertQueued
thay vì assertSent
:
Mail::assertQueued(...);
Mail::assertNotQueued(...);
Bạn có thể sử dụng phương thức fake
của facade Notification
để ngăn notification được gửi đi. Sau đó, bạn có thể xác nhận notifications đã được gửi cho người dùng hay chưa và thậm chí kiểm tra dữ liệu nó nhận được. Khi sử dụng fake, các assertion được thực hiện sau khi code test được thực thi:
<?php
namespace Tests\Feature;
use App\Notifications\OrderShipped;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Notification::fake();
// Assert that no notifications were sent...
Notification::assertNothingSent();
// Perform order shipping...
// Assert a specific type of notification was sent meeting the given truth test...
Notification::assertSentTo(
$user,
function (OrderShipped $notification, $channels) use ($order) {
return $notification->order->id === $order->id;
}
);
// Assert a notification was sent to the given users...
Notification::assertSentTo(
[$user], OrderShipped::class
);
// Assert a notification was not sent...
Notification::assertNotSentTo(
[$user], AnotherNotification::class
);
// Assert a notification was sent via Notification::route() method...
Notification::assertSentTo(
new AnonymousNotifiable, OrderShipped::class
);
// Assert Notification::route() method sent notification to the correct user...
Notification::assertSentTo(
new AnonymousNotifiable,
OrderShipped::class,
function ($notification, $channels, $notifiable) use ($user) {
return $notifiable->routes['mail'] === $user->email;
}
);
}
}
Thay cho việc làm giả, bạn có thể sử dụng phương thức fake
của facade Queue
để ngăn các job được queue. Sau đó, bạn có thể xác nhận các job đó đã được tạo trong queue hay chưa và thậm chí kiểm tra dữ liệu nó nhận được. Khi sử dụng fake, các assertion sẽ được thực hiện sau khi code test được thực thi:
<?php
namespace Tests\Feature;
use App\Jobs\AnotherJob;
use App\Jobs\FinalJob;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testOrderShipping()
{
Queue::fake();
// Assert that no jobs were pushed...
Queue::assertNothingPushed();
// Perform order shipping...
// Assert a specific type of job was pushed meeting the given truth test...
Queue::assertPushed(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
// Assert a job was pushed to a given queue...
Queue::assertPushedOn('queue-name', ShipOrder::class);
// Assert a job was pushed twice...
Queue::assertPushed(ShipOrder::class, 2);
// Assert a job was not pushed...
Queue::assertNotPushed(AnotherJob::class);
// Assert a job was pushed with a given chain of jobs, matching by class...
Queue::assertPushedWithChain(ShipOrder::class, [
AnotherJob::class,
FinalJob::class
]);
// Assert a job was pushed with a given chain of jobs, matching by both class and properties...
Queue::assertPushedWithChain(ShipOrder::class, [
new AnotherJob('foo'),
new FinalJob('bar'),
]);
// Assert a job was pushed without a chain of jobs...
Queue::assertPushedWithoutChain(ShipOrder::class);
}
}
Phương thức fake
của facade Storage
cho phép bạn dễ dàng tạo một disk giả, kết hợp với các tiện ích tạo file của class UploadedFile
, sẽ giúp đơn giản hóa rất nhiều việc kiểm tra file upload. Ví dụ:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function testAlbumUpload()
{
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// Assert one or more files were stored...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// Assert one or more files were not stored...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
}
}
{tip} Mặc định, phương thức
fake
sẽ xóa tất cả các file trong thư mục temporary của nó. Nếu bạn muốn giữ lại các file này, bạn có thể sử dụng phương thức "persistentFake" thay thế.
Không giống như các phương thức static call truyền thống, facades có thể bị làm giả. Điều này cung cấp một lợi thế lớn so với các phương thức static truyền thống và cho phép bạn khả năng test nếu bạn đang sử dụng khai báo phụ thuộc. Khi test, bạn có thể muốn làm giả việc gọi đến facade của Laravel trong controller của bạn. Ví dụ, hãy xem hành động của controller sau:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Show a list of all users of the application.
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
Chúng ta có thể làm giả việc gọi đến facade Cache
bằng cách sử dụng phương thức shouldReceive
, nó sẽ trả về một instance giả của Mockery. Vì các facade được resolve và quản lý bởi service container, nên chúng có khả năng test cao hơn nhiều so với một class static thông thường. Ví dụ: chúng ta hãy làm giả việc gọi đến phương thức get
của facade Cache
:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Cache;
use Tests\TestCase;
class UserControllerTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$response = $this->get('/users');
// ...
}
}
{note} Bạn không nên làm giả facade
Request
. Thay vào đó, hãy truyền input mà bạn mong muốn vào phương thức của HTTP helper, chẳng hạn nhưget
vàpost
khi chạy test của bạn. Tương tự như vậy, thay vì làm giả facadeConfig
, hãy gọi phương thứcConfig::set
trong các test của bạn.
entry