PHPUnit 自動化測試大作戰【CH16】

更新於 發佈於 閱讀時間約 8 分鐘

今天我們來聊聊「Mocking」吧!

何為 Mocking & 為何 Mocking

所謂的 Mocking,是指用各種方式來模擬它原本的行為與功能,藉此將我們要測試的對象,與其相依的外部服務「隔離」。簡單來說,就是做一個外部服務的「仿冒品」。這裡的外部服務,可以是其他類別函數、外部API、檔案系統存取介面等等。

那為什麼需要 Mocking呢?大家可以試想幾種情境:

  • 假如我們有一個API,它會呼叫外部服務A Service,如果某天A Service停機維修,而我們沒有做Mocking A Service,那我們所寫的測試程式是否就會隨之停擺或出錯?
  • 同樣的情況,假如我們要測試當A Service掛點時,我們的API仍然可以某種方式正常運作,在無法打電話給A Service廠商,請他們停機時,此時該如何測試以上行為?
  • 同樣的情況,假如 A Service 其實還沒上線,只知道預期中的介面與行為,此時我們該如何測試?

在以上的情況中,有一個很重要的共通點,那就是測試能否運行,都依賴於外部服務是否為我們所控制。為了破除這個依賴,Mocking 就由此而生了!

Mocking 初體驗

以下讓我們一個Mocking的例子:

  • app/Repositories/UserRepository.php
<?php

namespace App\Repositories;

use App\Models\User;

class UserRepository{

protected $model;

public function __construct(User $model) {

$this->model = $model;

} public function getUserById($userId) {

return $this->model::find($userId);

}}
  • app/Repositories/PostRepository.php
<?php

namespace App\Repositories;

class PostRepository{

protected $model;

}
  • app/Services/UserService.php
<?php

namespace App\Services;

use App\Repositories\PostRepository;

use App\Repositories\UserRepository;

class UserService{

private $userRepository;

private $postRepository;

public function __construct( PostRepository $postRepository, UserRepository $userRepository ) {

$this->postRepository = $postRepository;

$this->userRepository = $userRepository;

} public function getUserData(int $userId) {

$user = $this->userRepository->getUserById($userId);

if (empty($user)) {

return [];

} $user->posts = $this->postRepository->getPostsByUserId($userId);

return $user;

}}
  • tests/Feature/UserServiceTest.php
<?php

namespace Tests\Feature;

use App\Models\User;

use App\Repositories\PostRepository;

use App\Repositories\UserRepository;

use App\Services\UserService;

use Tests\TestCase;

class UserServiceTest extends TestCase{

public function testGetUserDataWhenUserNotFound() {

$this->mock(UserRepository::class, function ($mock) {

$mock->shouldReceive('getUserById')

->with(1)

->once()

->andReturn(null);

}); $service = app(UserService::class);

$user = $service->getUserData(1);

$this->assertEmpty($user);

} public function testGetUserData() {

$user = User::factory()->make();

$this->mock(UserRepository::class, function ($mock) use ($user) {

$mock->shouldReceive('getUserById')

->with(1)

->once()

->andReturn($user);

}); $this->mock(PostRepository::class, function ($mock) {

$mock->shouldReceive('getPostsByUserId')

->with(1)

->once()

->andReturn([]);

}); $service = app(UserService::class);

$user = $service->getUserData(1);

$this->assertNotEmpty($user);

$this->assertNotNull($user->posts);

}}

在以上測試程式碼中,我們撰寫了 2 個測試案例函數:

  • testGetUserDataWhenUserNotFound()在這個函數中,我們 Mock 了 UserRepository→getUserById() 這個函數的行為,因此即便我們沒有準備測試資料,仍然可以測試「UserRepository→getUserById(1) 的回應為 null 」的這個情境下,UserService→getUserData(1) 是否可正常運作。
  • testGetUserData()在這個函數中,這次我們 Mock 了 UserRepository→getUserById() 以 PostRepository→getPostsByUserId()及這2個函數的行為,而這次我們甚至 Mock 了一個還沒實作的函數 PostRepository→getPostsByUserId() !

以上便是今天的 Mocking 初體驗,是不是很有趣呢?

下一篇讓我們繼續探所更多關於 Mocking 的各種技巧吧!

如果您喜歡這篇文章,歡迎加入追蹤以接收新文章通知 😄

參考資料

本系列文章目錄

留言
avatar-img
留言分享你的想法!
avatar-img
WilliamP的沙龍
13會員
495內容數
歡迎來到 WilliamP 的沙龍天地,在這裡將與各位讀者探討各種主題,包刮高中數學題庫、PHP開發經驗、LINE聊天機器人開發經驗、書摘筆記等,歡迎交流!
WilliamP的沙龍的其他內容
2023/12/18
在前一篇文章中,我們探討了多重資料庫連線情境下,Model 及 Database Assertion 的應對方式,不過實際上筆者認為比較有難度的,其實是 Migration 應對方式。 今天就讓我們來探討這部分吧! Migration 應對方式 對於多重資料庫連線這種情境,筆者實務上做過的對應
2023/12/18
在前一篇文章中,我們探討了多重資料庫連線情境下,Model 及 Database Assertion 的應對方式,不過實際上筆者認為比較有難度的,其實是 Migration 應對方式。 今天就讓我們來探討這部分吧! Migration 應對方式 對於多重資料庫連線這種情境,筆者實務上做過的對應
2023/12/18
今天讓我們探討「缺乏 Migration Files 與 Factory Files」的 Legacy 情境吧! 很多時候我們會遇到沒有 Migration Files 或 Factory Files 的 Legacy Codebase,原因大概有以下幾種: 該程式庫原本不是以 Laravel
2023/12/18
今天讓我們探討「缺乏 Migration Files 與 Factory Files」的 Legacy 情境吧! 很多時候我們會遇到沒有 Migration Files 或 Factory Files 的 Legacy Codebase,原因大概有以下幾種: 該程式庫原本不是以 Laravel
2023/12/18
在實務情境上,常會有在單一專案程式庫中,存取多個不同資料庫的使用情境,在這種情況下,我們通常會設置多個資料庫連線(Database Connection)設定。 在平常開發使用設很方便,但要做測試時就會發現一些問題: 在測試程式碼或 Seeder 中調用 factory() 時,都是在預設連線資
2023/12/18
在實務情境上,常會有在單一專案程式庫中,存取多個不同資料庫的使用情境,在這種情況下,我們通常會設置多個資料庫連線(Database Connection)設定。 在平常開發使用設很方便,但要做測試時就會發現一些問題: 在測試程式碼或 Seeder 中調用 factory() 時,都是在預設連線資
看更多
你可能也想看
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
當我們架好站、WebService測試完,接著就是測試區域網路連線啦~
Thumbnail
建立自己的Module有哪些好處?
Thumbnail
建立自己的Module有哪些好處?
Thumbnail
  上回練習了一個官方示例,但其中對於一些細節沒有練習到的感覺,這次我們實際將之前練習的風格轉換訓練推上去看看,看是否能體驗到更多細節。
Thumbnail
  上回練習了一個官方示例,但其中對於一些細節沒有練習到的感覺,這次我們實際將之前練習的風格轉換訓練推上去看看,看是否能體驗到更多細節。
Thumbnail
代理模式通過封裝原始對象來實現對該對象的控制和管理,同時不改變原始對象的行為或客戶端與該對象互動的方式,以此介入或增強對該對象的訪問和操作。
Thumbnail
代理模式通過封裝原始對象來實現對該對象的控制和管理,同時不改變原始對象的行為或客戶端與該對象互動的方式,以此介入或增強對該對象的訪問和操作。
Thumbnail
進入物件導向設計的實戰階段,我們通過建立人力資源管理功能來實踐理論知識。透過這些實作練習,能夠深化對物件導向概念的理解,並學會如何在實際開發中應用這些概念。
Thumbnail
進入物件導向設計的實戰階段,我們通過建立人力資源管理功能來實踐理論知識。透過這些實作練習,能夠深化對物件導向概念的理解,並學會如何在實際開發中應用這些概念。
Thumbnail
這篇文章將會講述虛擬(virtual)與覆蓋(override)的簡易使用方式。
Thumbnail
這篇文章將會講述虛擬(virtual)與覆蓋(override)的簡易使用方式。
Thumbnail
這篇要測試的工作流是Face Swap。
Thumbnail
這篇要測試的工作流是Face Swap。
Thumbnail
是否有時遇到一種情況,發現一種似乎有一定勝率的交易技術線型, 想回測時,若軟體沒有提供相關線型回測,就必須自己寫程式來呈現線型,進而回測 然而卻不知道如何用程式的方式將他呈現出來。 今天用XQ來做個簡單分享: 弄清楚運作模式 在懂得基本變數的情況下,最重要的是懂得程式是怎麼運作的, 無
Thumbnail
是否有時遇到一種情況,發現一種似乎有一定勝率的交易技術線型, 想回測時,若軟體沒有提供相關線型回測,就必須自己寫程式來呈現線型,進而回測 然而卻不知道如何用程式的方式將他呈現出來。 今天用XQ來做個簡單分享: 弄清楚運作模式 在懂得基本變數的情況下,最重要的是懂得程式是怎麼運作的, 無
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News