為了提升系統穩定性,最近替某電商客戶的專案寫單元測試
有一部份核心功能會用到第三方 API,這讓測試變很難寫,因為沒有本地的狀態變化可以比較
想到 laravel 有一個關於 mail 的測試功能,有類似情況
簡單來說就是在 phpunit 中這樣啟動之後
Mail::fake();
就可以這樣來檢查是否有執行某 api
Mail::assertSent(OrderShipped::class);
能否用同樣原理,處理我面對的情況呢?
決定來看一下原始碼!
首先是 fake 會直接換掉 Mailer 的實體
framework/src/Illuminate/Support/Facades/Mail.php
public static function fake()
{
$actualMailManager = static::isFake()
? static::getFacadeRoot()->manager
: static::getFacadeRoot();
return tap(new MailFake($actualMailManager), function ($fake) {
static::swap($fake);
});
}
可見是實作同樣 interface 的測試專用類別!
framework/src/Illuminate/Support/Testing/Fakes/MailFake.php
然後寄信就改成,把變數存進陣列即可
public function send($view, array $data = [], $callback = null)
{
if (! $view instanceof Mailable) {
return;
}
$view->mailer($this->currentMailer);
if ($view instanceof ShouldQueue) {
return $this->queue($view, $data);
}
$this->currentMailer = null;
$this->mailables[] = $view;
}
然後 assertSent 那些就檢查陣列內容,比對一下即可
public function assertSent($mailable, $callback = null)
{
[$mailable, $callback] = $this->prepareMailableAndCallback($mailable, $callback);
if (is_numeric($callback)) {
return $this->assertSentTimes($mailable, $callback);
}
$message = "The expected [{$mailable}] mailable was not sent.";
if (count($this->queuedMailables) > 0) {
$message .= ' Did you mean to use assertQueued() instead?';
}
PHPUnit::assertTrue(
$this->sent($mailable, $callback)->count() > 0,
$message
);
}
inside of the sent
function
return $this->mailablesOf($mailable)->filter(fn ($mailable) => $callback($mailable));
所以,目前這樣看起來,在需要測試跟「第三方 API」有關的功能時,可以使用同樣原理
使用 laravel 提供的 mock 功能,把會用到的第三方套件 mock 掉,套件的核心 function 就存個變數或陣列之類的
然後多寫個類似 assertSent
這樣的方法,應該就可以做到不錯的測試效果~!
簡單來說,就是設法做出「本地的狀態變化可以比較」就是了
我來往這方向寫寫看,有心得再補充分享~