Laravel - Testing - Mocking

# 前言

學習一個框架, Ray 的想法是, 在深入理解底層實作的原理之前, 應該先知道這個框架的 使用方法; 先學習怎麼使用這個前人造的輪子, 再學習怎麼樣一個輪子。
所以本篇文章重點在於細讀官方文件, 並將內容理解後以 Q&A 的方式記錄下來, 加速學習以及查詢。

# 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, 定義當前時間以方便測試

Laravel - Package - Telescope Laravel - Digging Deeper - Localization (官方文件原子化翻譯筆記)

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×