昨天我們完成了 Roman Numeral Kata,現在進入框架特定測試的新階段!想像一個場景:專案上線前夕,PM 緊張地問:「API 都測試過了嗎?」你自信地回答:「每個端點都有完整的測試覆蓋!」🎯 這就是今天要學習的 HTTP 測試。
基礎階段 Kata 階段 框架特定測試
Days 1-10 Days 11-17 Days 18-27
✅ ✅ 📍 Day 18
[HTTP 測試基礎] <- 今天在這
⬇️
資料庫測試設置
⬇️
實戰應用開發
在 Laravel 中,HTTP 測試允許我們:
// 建立 tests/Feature/Day18/HttpTestingBasicsTest.php
<?php
namespace Tests\Feature\Day18;
use Tests\TestCase;
test('makes successful GET request', function () {
$response = $this->get('/api/health');
expect($response->status())->toBe(200);
});
// 建立 routes/api.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/health', function () {
return response()->json(['status' => 'ok']);
});
// 更新 tests/Feature/Day18/HttpTestingBasicsTest.php
test('supports different HTTP methods', function () {
// GET 請求
$response = $this->get('/api/users');
expect($response->status())->toBe(200);
// POST 請求
$response = $this->post('/api/users', [
'name' => 'John Doe',
'email' => 'john@example.com'
]);
expect($response->status())->toBe(201);
// PUT 請求
$response = $this->put('/api/users/1', [
'name' => 'Jane Doe'
]);
expect($response->status())->toBe(200);
// DELETE 請求
$response = $this->delete('/api/users/1');
expect($response->status())->toBe(204);
});
test('sends and receives JSON data', function () {
// 發送 JSON 請求
$response = $this->postJson('/api/users', [
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 30
]);
// 驗證狀態碼
expect($response->status())->toBe(201);
// 驗證 JSON 結構
$response->assertJsonStructure([
'data' => ['id', 'name', 'email', 'age', 'created_at']
]);
// 驗證 JSON 內容
$response->assertJson([
'data' => [
'name' => 'John Doe',
'email' => 'john@example.com'
]
]);
});
test('validates response content', function () {
$response = $this->get('/api/products');
$response->assertOk()
->assertSee('Product Name')
->assertJsonPath('data.0.name', 'Product 1')
->assertHeader('Content-Type', 'application/json');
});
// 建立 app/Http/Controllers/Day18/TaskController.php
<?php
namespace App\Http\Controllers\Day18;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class TaskController extends Controller
{
private static $tasks = [];
public function index()
{
return response()->json(['data' => self::$tasks]);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'completed' => 'boolean'
]);
$task = [
'id' => count(self::$tasks) + 1,
'title' => $validated['title'],
'completed' => $validated['completed'] ?? false,
'created_at' => now()->toDateTimeString()
];
self::$tasks[] = $task;
return response()->json(['data' => $task], 201);
}
}
// 更新 routes/api.php
use App\Http\Controllers\Day18\TaskController;
Route::prefix('tasks')->group(function () {
Route::get('/', [TaskController::class, 'index']);
Route::post('/', [TaskController::class, 'store']);
});
// 建立 tests/Feature/Day18/TaskApiTest.php
<?php
namespace Tests\Feature\Day18;
use Tests\TestCase;
test('lists all tasks', function () {
$response = $this->getJson('/api/tasks');
$response->assertOk()
->assertJsonStructure(['data']);
});
test('creates new task', function () {
$taskData = [
'title' => 'Learn HTTP Testing',
'completed' => false
];
$response = $this->postJson('/api/tasks', $taskData);
$response->assertCreated()
->assertJsonPath('data.title', 'Learn HTTP Testing')
->assertJsonPath('data.completed', false)
->assertJsonStructure([
'data' => ['id', 'title', 'completed', 'created_at']
]);
});
test('validates task creation input', function () {
$response = $this->postJson('/api/tasks', []);
$response->assertStatus(422)
->assertJsonValidationErrors(['title']);
});
test('retrieves specific task', function () {
$createResponse = $this->postJson('/api/tasks', ['title' => 'Test Task']);
$taskId = $createResponse->json('data.id');
$this->getJson("/api/tasks/{$taskId}")
->assertOk()
->assertJsonPath('data.title', 'Test Task');
});
test('returns 404 for nonexistent task', function () {
$response = $this->getJson('/api/tasks/999');
$response->assertNotFound()
->assertJson(['error' => 'Task not found']);
});
test('updates task', function () {
$createResponse = $this->postJson('/api/tasks', ['title' => 'Original Title']);
$taskId = $createResponse->json('data.id');
$this->putJson("/api/tasks/{$taskId}", ['title' => 'Updated Title'])
->assertOk()
->assertJsonPath('data.title', 'Updated Title');
});
test('deletes task', function () {
$createResponse = $this->postJson('/api/tasks', ['title' => 'Task to Delete']);
$taskId = $createResponse->json('data.id');
$this->deleteJson("/api/tasks/{$taskId}")->assertNoContent();
$this->getJson("/api/tasks/{$taskId}")->assertNotFound();
});
// 更新 tests/Feature/Day18/ChainedTest.php
use function Pest\Laravel\{get, post, put, delete};
it('demonstrates chained syntax', function () {
get('/api/health')
->assertOk()
->assertJson(['status' => 'ok']);
post('/api/tasks', ['title' => 'New Task'])
->assertCreated()
->assertJsonPath('data.title', 'New Task');
$response = post('/api/tasks', ['title' => 'Chain Task']);
$id = $response->json('data.id');
get("/api/tasks/{$id}")->assertOk();
put("/api/tasks/{$id}", ['completed' => true])->assertOk();
delete("/api/tasks/{$id}")->assertNoContent();
});
test('protects routes with authentication', function () {
$response = $this->getJson('/api/protected');
$response->assertUnauthorized();
});
test('handles file uploads', function () {
Storage::fake('uploads');
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->post('/api/upload', ['avatar' => $file]);
$response->assertOk();
Storage::disk('uploads')->assertExists('avatars/' . $file->hashName());
});
試著實作這些功能的測試:
今天我們學習了 Laravel HTTP 測試的基礎:
HTTP 測試是 API 開發的基石,讓我們能在不啟動伺服器的情況下,完整測試 API 的行為。這為我們接下來的實戰應用開發打下了堅實的基礎。
明天我們將學習資料庫測試設置,包括測試資料庫的配置、Factory 的使用,以及如何在測試中管理資料庫狀態。準備好深入資料層測試了嗎?明天見!🚀