昨天我們學會了 HTTP 測試的基礎,但你有沒有想過:「如果測試需要發送 Email 怎麼辦?」「測試時真的要上傳檔案到雲端嗎?」「測試付款功能要真的扣款嗎?」今天要學的 Mock 和 Fake 就是解決這些問題的關鍵!
基礎階段 Kata 階段 框架特定測試
Days 1-10 Days 11-17 Days 18-27
✅ ✅ 📍 Day 19
HTTP 測試基礎
⬇️
[Mock & Fake] <- 今天在這
⬇️
資料庫測試設置
想像你正在測試一個訂單系統:
如果每次測試都真的執行這些動作,會發生什麼事?
// ❌ 問題重重的測試
test('createsOrderAndSendsEmail', function () {
$order = Order::create([...]);
// 真的發信?測試信箱會爆滿!
Mail::to($user)->send(new OrderConfirmation($order));
// 真的扣款?測試環境也要花錢?
$payment->charge(1000);
// 真的上傳?測試檔案會塞滿儲存空間!
Storage::put('invoices/invoice.pdf', $pdf);
});
Laravel 提供了強大的 Mock 和 Fake 機制,讓我們能安全地測試這些功能。
// 建立 tests/Feature/Day19/FakeBasicsTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Queue;
test('understandsFakeConcept', function () {
// Fake 會攔截實際操作,記錄所有動作
Mail::fake();
Storage::fake('s3');
Queue::fake();
// 程式碼照常執行,但不會真的發信、上傳或排程
// 所有動作都被記錄下來供測試驗證
});
// 建立 tests/Feature/Day19/MailFakeTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Mail;
use App\Mail\Day19\WelcomeMail;
test('sendsWelcomeEmail', function () {
Mail::fake();
// 執行會發送郵件的動作
Mail::to('user@example.com')->send(new WelcomeMail('Alice'));
// 驗證郵件已發送
Mail::assertSent(WelcomeMail::class);
// 驗證發送次數
Mail::assertSentCount(1);
// 驗證收件者
Mail::assertSent(WelcomeMail::class, function ($mail) {
return $mail->hasTo('user@example.com');
});
// 驗證郵件內容
Mail::assertSent(WelcomeMail::class, function ($mail) {
return $mail->userName === 'Alice';
});
});
test('doesNotSendEmailWhenConditionFails', function () {
Mail::fake();
$shouldSend = false;
if ($shouldSend) {
Mail::to('user@example.com')->send(new WelcomeMail('Bob'));
}
// 驗證沒有發送郵件
Mail::assertNotSent(WelcomeMail::class);
Mail::assertNothingSent();
});
// 建立 tests/Feature/Day19/StorageFakeTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\UploadedFile;
test('storesUploadedFile', function () {
Storage::fake('local');
$file = UploadedFile::fake()->image('avatar.jpg', 100, 100);
// 儲存檔案
$path = Storage::disk('local')->put('avatars', $file);
// 驗證檔案存在
Storage::disk('local')->assertExists($path);
// 驗證檔案不存在
Storage::disk('local')->assertMissing('avatars/non-existent.jpg');
});
// 建立 tests/Feature/Day19/QueueFakeTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Queue;
use App\Jobs\Day19\ProcessPayment;
test('dispatchesPaymentJob', function () {
Queue::fake();
// 分派工作
ProcessPayment::dispatch(123, 99.99);
// 驗證工作已分派
Queue::assertPushed(ProcessPayment::class);
// 驗證分派次數
Queue::assertPushed(ProcessPayment::class, 1);
// 驗證工作參數
Queue::assertPushed(ProcessPayment::class, function ($job) {
return $job->orderId === 123
&& $job->amount === 99.99;
});
});
// 建立 tests/Feature/Day19/HttpFakeTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Http;
test('callsExternalAPI', function () {
Http::fake([
'api.example.com/*' => Http::response([
'status' => 'success',
'data' => ['id' => 1]
], 200)
]);
$response = Http::get('https://api.example.com/users');
expect($response->successful())->toBeTrue();
expect($response->json('status'))->toBe('success');
expect($response->json('data.id'))->toBe(1);
// 驗證請求已發送
Http::assertSent(function ($request) {
return $request->url() === 'https://api.example.com/users';
});
});
// 建立 tests/Feature/Day19/EventFakeTest.php
<?php
namespace Tests\Feature\Day19;
use Tests\TestCase;
use Illuminate\Support\Facades\Event;
use App\Events\Day19\OrderCreated;
test('dispatchesOrderCreatedEvent', function () {
Event::fake();
// 觸發事件
OrderCreated::dispatch(456, 'customer@example.com');
// 驗證事件已觸發
Event::assertDispatched(OrderCreated::class);
// 驗證事件資料
Event::assertDispatched(OrderCreated::class, function ($event) {
return $event->orderId === 456
&& $event->customerEmail === 'customer@example.com';
});
});
// 建立 tests/Feature/Day19/OrderServiceTest.php
test('orderServiceCreatesCompleteOrder', function () {
// 設置所有 Fake
Mail::fake();
Storage::fake('local');
Event::fake();
Queue::fake();
// 執行訂單建立邏輯
$result = createOrder(['email' => 'buyer@example.com', 'amount' => 299.99]);
// 驗證所有環節
Mail::assertSent(OrderConfirmation::class);
Event::assertDispatched(OrderCreated::class);
Storage::assertExists("invoices/{$result['order_id']}.txt");
Queue::assertPushed(ProcessPayment::class);
});
試著實作一個通知系統,包含:
test('sendsNotificationsThroughPreferredChannels', function () {
// 實作你的測試...
});
今天我們學習了 Laravel 強大的 Mock 和 Fake 系統:
明天我們將深入資料庫測試,學習如何使用 Factory、Seeder,以及如何管理測試資料庫的狀態。準備好了嗎?讓我們繼續前進!