Ray Lee | 李宗叡
Learn or Die
Published in
23 min readApr 4, 2021

--

Photo by Brett Jordan on Unsplash

# 版本

Laravel 8.x

# 前言

我喜歡使用 Laravel 開發的感覺, 除了開發快速, 程式碼簡潔且優雅之外, Laravel 框架本身也是一個很好的學習參照物。 本篇主要將官方文件重點整理成 Q&A 的形式呈現, 原子化的概念, 這方式並不適用於每個人, 但若對你有幫助, 我會很開心。

# 目錄

Laravel — 官方文件原子化翻譯 — 目錄

# Mocking Objects

以下的 Laravel example code 的意思是?

  • Example:
<?php
use App\Service;
use Mockery;
use Mockery\MockInterface;

public function test_something_can_be_mocked()
{
$this->instance(
Service::class,
Mockery::mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
})
);
}
  • Answer: 將 service::class 綁到 service container 當中, 當完成綁定後, service container 將會使用 mocked class, 而不是原本的 object, 並指定 process method 會被呼叫一次

以下的 Laravel example code 的意思是?

  • Example:
<?php
use App\Service;
use Mockery\MockInterface;

$mock = $this->mock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
  • Answer: 將 service::class 綁到 service container 當中, 當完成綁定後, service container 將會使用 mocked class, 而不是原本的 object, 並指定 process method 會被呼叫一次

以下的 Laravel example code 的意思是?

  • Example:
<?php
use App\Service;
use Mockery\MockInterface;

$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
$mock->shouldReceive('process')->once();
});
  • Answer: 使用 partialMock() 將 Service::class 綁定 container, 所以 container 會使用 mocked class, 而不是實際上的 object, 並使用 shouldReceive() 宣告 process method 會被呼叫, once() 定義次數 partialMock 只 mock 被呼叫的 method, 其他 method 如果在 testing 過程中有被呼叫, 則正常執行

以下的 Laravel example code 的意思是?

  • Example:
<?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');

// ...
}
}
  • Answer: mock Cache facade, 並使用 shouldReceive 斷言 get() 將被呼叫, once() 代表次數, with() 代表 parameter, 而 return value 代表回傳的值, 如果沒有達到以上的斷言, 則 fail

Laravel testing 中, 如果要 mock config, 該使用?

config::set

Laravel testing 中, 如果要 mock http testing, 該使用?

http testing method

# Facade Spies

以下的 Laravel example code 的意思是?

  • Example:
<?php
use Illuminate\Support\Facades\Cache;

public function test_values_are_be_stored_in_cache()
{
Cache::spy();

$response = $this->get('/');

$response->assertStatus(200);

Cache::shouldHaveReceived('put')->once()->with('name', 'Taylor', 10);
}
  • Answer: 使用 spy() method 來紀錄下所有 testing 過程與 Cache facade 的互動, 並在最後 assert, 若不符合 assertion 則報錯

# Bus Fake

以下的 Laravel example code 的意思是?

  • Example:
<?php

namespace Tests\Feature;

class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped()
{
Bus::fake();

// Perform order shipping...

Bus::assertDispatched(ShipOrder::class);

Bus::assertNotDispatched(AnotherJob::class);
}
}
  • Answer: 使用 Bus facade 的 fake(), 任何使用到 Bus facade dispatch 的 job 將不會真正的被 dispatch 最後可使用 assertDispatched(), assertNotDispatched() 來斷言哪個 job 被 dispatch 了, 哪個沒有

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Bus::assertDispatched(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
  • Answer: 當使用 Bus facade 的 assertDispatched(), assertNotDispatched() 時, 可帶入 closure 來判定被 dispatched 的 job 有通過一定的規則

# Job Chains

以下的 Laravel example code 的意思是?

  • Example:
<?php
use App\Jobs\RecordShipment;
use App\Jobs\ShipOrder;
use App\Jobs\UpdateInventory;
use Illuminate\Support\Facades\Bus;

Bus::assertChained([
ShipOrder::class,
RecordShipment::class,
UpdateInventory::class
]);
  • Answer: assert 指定的 job 有被 chained 且 dispatched

以下的 Laravel example code 的意思是?

  • Example:
<?php
Bus::assertChained([
new ShipOrder,
new RecordShipment,
new UpdateInventory,
]);
  • Answer: assert 指定的 job 有被 chained 且 dispatched, 除了可帶入 class name, 也可帶入 class instance

# Job Batches

以下的 Laravel example code 的意思是?

  • Example:
<?php
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;

Bus::assertBatched(function (PendingBatch $batch) {
return $batch->name == 'import-csv' &&
$batch->jobs->count() === 10;
});
  • Answer: 使用 assertBatched() 來斷言 batch of jobs 已被 dispatched, 可在 closure 內取得 PendingBatch, 取得該 batch 的資料, 並定義該 batch 應該要有的條件

# Event Fake

以下的 Laravel example code 的意思是?

  • Example:
<?php

class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped()
{
Event::fake();

// Perform order shipping...

Event::assertDispatched(OrderShipped::class);

Event::assertDispatched(OrderShipped::class, 2);

Event::assertNotDispatched(OrderFailedToShip::class);

Event::assertNothingDispatched();
}
}
  • Answer: 使用 fake(), 則該 event 的 listener 將不會真正的 dispatch assertDispatched() 斷言指定的 event 需被 dispatched assertDispatched() arg2 代表該 event 需被 dispatched 2 次 assertNotDispatched 斷言該 event 沒被 dispatched assertNothingDispatched 斷言沒有任何 event 被 dispatched

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});
  • Answer: 使用 closure 來斷言, 符合定義條件的 job 有被 dispatched

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Event::assertListening(
OrderShipped::class,
[SendShipmentNotification::class, 'handle']
);
  • Answer: 斷言指定的 listener 有 listen 定義的 event

以下的 Laravel testing 中, 如果有使用到 Factory 的 model event, Event::fake() 需使用在 Factory 之後, 原因為何?

因為一旦使用了 Event::fake(), 所有 event 都不會被執行

# Faking A Subset Of Events

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function test_orders_can_be_processed()
{
Event::fake([
OrderCreated::class,
]);

$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

// Other events are dispatched as normal...
$order->update([...]);
}
  • Answer: 可帶入 class 到 Event::fake(), 這樣只有該 event 會被 fake, 其餘的 event 照常執行

Scoped Event Fakes

以下的 Laravel testing example code 的意思是?

  • Example:
<?php

class ExampleTest extends TestCase
{
public function test_orders_can_be_processed()
{
$order = Event::fakeFor(function () {
$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

return $order;
});

$order->update([...]);
}
}
  • Answer: 只有 fakeFor() 內, closure 範圍內的 event 才會被 fake, 其餘的照常執行

# HTTP Fake

可參考 Fake HTTP Client

# Mail Fake

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function test_orders_can_be_shipped()
{
Mail::fake();

Mail::assertNothingSent();

Mail::assertSent(OrderShipped::class);

Mail::assertSent(OrderShipped::class, 2);

Mail::assertNotSent(AnotherMailable::class);
}
  • Answer: 使用 Mail::fake(), 模擬 mail 寄出 testing, 實際上不會真的寄出 assert 沒有任何 mailable 被送出 assert 指定的 mailable 被發送 assert 指定的 mailable 被發送兩次 assert 指定的 mailable 沒有被送出

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Mail::assertQueued(OrderShipped::class);

Mail::assertNotQueued(OrderShipped::class);

Mail::assertNothingQueued();
  • Answer: 當要測試用 queue 發送的 mailable 時, 使用 assertQueued() assert OrderShipped mailable 由 queue 發送 assert OrderShipped mailable 沒有由 queue 發送 assert 沒有任何 mailable 經由 queue 被發送

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});
  • Answer: assertSent 以及 assertNotSent 也可帶入 closure, 判斷是否有通過 closure 內條件的 mailable 被發送

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
return $mail->hasTo($user->email) &&
$mail->hasCc('...') &&
$mail->hasBcc('...');
});
  • Answer: 除了 assert 指定 mailable 被送出外, 還可以 assert 寄給誰 cc 給誰 bcc 給誰

# Notification Fake

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function test_orders_can_be_shipped()
{
Notification::fake();

// Perform order shipping...

Notification::assertNothingSent();

Notification::assertSentTo(
[$user], OrderShipped::class
);

Notification::assertNotSentTo(
[$user], AnotherNotification::class
);
}
  • Answer: 使用 Notification::fake() 斷言沒有任何 notification sent 斷言指定的 notification sent to a given user 斷言指定的 notification not sent to a given user

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Notification::assertSentTo(
$user,
function (OrderShipped $notification, $channels) use ($order) {
return $notification->order->id === $order->id;
}
);
  • Answer: assertSentTo 可帶入 closure, 若有符合 closure 內條件的 mailable sent, 則 assertion 成立

# On-Demand Notifications

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
use Illuminate\Notifications\AnonymousNotifiable;

Notification::assertSentTo(
new AnonymousNotifiable, OrderShipped::class
);
  • Answer: 當測試 On-Demand notification 時, 原本帶入 user 的 arg1, 改帶入 AnonymousNotifiable instance

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Notification::assertSentTo(
new AnonymousNotifiable,
OrderShipped::class,
function ($notification, $channels, $notifiable) use ($user) {
return $notifiable->routes['mail'] === $user->email;
}
);
  • Answer: 當測試 on-demand notification 時, 可帶入 closure, assert 實際上發送的 mail 與指定 user 的 mail 相同

# Queue Fake

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function test_orders_can_be_shipped()
{
Queue::fake();

// Perform order shipping...

Queue::assertNothingPushed();

Queue::assertPushedOn('queue-name', ShipOrder::class);

Queue::assertPushed(ShipOrder::class, 2);

Queue::assertNotPushed(AnotherJob::class);
}
  • Answer: fake queue facade, 所以不會真的把 job push 到 queue 當中 assert 沒有任何 job 被 push assert ShipOrder job 被 push 到 queue-name assert ShipOrder job 被 push 兩次 assert AnotherJob 沒有被 push

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Queue::assertPushed(function (ShipOrder $job) use ($order) {
return $job->order->id === $order->id;
});
  • Answer: 可 pass closure 到 assertPushed(), assertNotPushed(), 更明確的斷言是哪些 job 被 push 或沒被 push

# Job Chains

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Queue::assertPushedWithChain(ShipOrder::class, [
RecordShipment::class,
UpdateInventory::class
]);
  • Answer: 斷言哪些 queue 被 queue chained, arg1 為第一個 job, arg2 為其餘的 jobs

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Queue::assertPushedWithChain(ShipOrder::class, [
new RecordShipment,
new UpdateInventory,
]);
  • Answer: assertPushedWithChain() 可 pass class name, 也可 pass instance

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
Queue::assertPushedWithoutChain(ShipOrder::class);
  • Answer: 使用 assertPushedWithoutChain() 來斷言 ShipOrder job 沒有被 chained

# Storage Fake

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function test_albums_can_be_uploaded()
{
Storage::fake('photos');

$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);

Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);

Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
}
  • Answer: 使用 Storage fake() 一個 fake disk 產生 fake image 斷言 fake disk photos 存在某些檔案 斷言 fake disk photos 不存在某些檔案

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::persistentFake()->image('photo2.jpg')
]);
  • Answer: 使用 fake 時, 該 fake file 會在 testing 結束後從 temp dir 中被刪除 若要保留, 可使用 persistentFake()

# Interacting With Time

以下的 Laravel testing example code 的意思是?

  • Example:
<?php
public function testTimeCanBeManipulated()
{
$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();
}
  • Answer:
<?php
public function testTimeCanBeManipulated()
{
// Travel into the future...
$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();

// Travel into the past...
$this->travel(-5)->hours();

// Travel to an explicit time...
$this->travelTo(now()->subHours(6));

// Return back to the present time...
$this->travelBack();
}

可使用 travel() method, 定義當前時間以方便測試

--

--

Ray Lee | 李宗叡
Learn or Die

It's Ray. I do both backend and frontend, but more focus on backend. I like coding, and would like to see the whole picture of a product.