「前端寫好了,後端也測試通過了,為什麼整合起來就是不能動?」
資深工程師微笑著說:「你有測試過前後端的合作默契嗎?」
經過 27 天的旅程,我們已經具備了扎實的測試基礎。今天,我們要為即將到來的前後端整合做準備。
基礎測試 ✅ → Kata 實戰 ✅ → 框架測試 ✅ → 【整合準備 📍】
Day 1-10 Day 11-17 Day 18-27 Day 28 ← 我們在這裡
<?php
namespace Tests\Feature\Day28;
use Tests\TestCase;
use App\Models\Todo;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('verify_all_api_endpoints_are_working', function () {
// GET /api/todos
$this->getJson('/api/todos')
->assertStatus(200)
->assertJsonStructure([
'data' => ['*' => ['id', 'title', 'completed']]
]);
// POST /api/todos
$response = $this->postJson('/api/todos', ['title' => 'Test Todo'])
->assertStatus(201);
$todoId = $response->json('data.id');
// PATCH /api/todos/{id}
$this->patchJson("/api/todos/{$todoId}", ['completed' => true])
->assertStatus(200)
->assertJsonFragment(['completed' => true]);
// DELETE /api/todos/{id}
$this->deleteJson("/api/todos/{$todoId}")->assertStatus(204);
$this->assertDatabaseMissing('todos', ['id' => $todoId]);
});
test('api_responses_follow_consistent_format', function () {
Todo::factory()->create(['title' => 'Test Todo']);
$this->getJson('/api/todos')
->assertJsonStructure([
'data' => [],
'meta' => ['total', 'page', 'per_page']
]);
});
test('error_responses_have_consistent_structure', function () {
$this->postJson('/api/todos', [])
->assertStatus(422)
->assertJsonStructure([
'message',
'errors' => ['title' => []]
]);
});
<?php
namespace Tests\Feature\Day28;
test('cors_headers_are_present', function () {
$response = $this->options('/api/todos', [], [
'Origin' => 'http://localhost:3000',
'Access-Control-Request-Method' => 'GET'
]);
$response->assertHeader('Access-Control-Allow-Origin')
->assertHeader('Access-Control-Allow-Methods');
});
test('preflight_requests_work_correctly', function () {
$response = $this->options('/api/todos', [], [
'Origin' => 'http://localhost:3000',
'Access-Control-Request-Method' => 'POST',
'Access-Control-Request-Headers' => 'Content-Type'
]);
$response->assertStatus(204)
->assertHeader('Access-Control-Allow-Headers');
});
<?php
namespace Tests\Feature\Day28;
use App\Models\User;
use Laravel\Sanctum\Sanctum;
test('authenticated_requests_work', function () {
$user = User::factory()->create();
Sanctum::actingAs($user);
$this->getJson('/api/user')
->assertOk()
->assertJson(['id' => $user->id]);
});
test('unauthenticated_requests_are_rejected', function () {
$this->getJson('/api/user')
->assertStatus(401)
->assertJson(['message' => 'Unauthenticated.']);
});
test('token_based_auth_works', function () {
$user = User::factory()->create();
$token = $user->createToken('test-token')->plainTextToken;
$this->withHeaders(['Authorization' => "Bearer {$token}"])
->getJson('/api/user')
->assertOk();
});
<?php
namespace Tests\Feature\Day28;
test('validates_string_length_limits', function () {
$longString = str_repeat('a', 256);
$this->postJson('/api/todos', ['title' => $longString])
->assertStatus(422)
->assertJsonValidationErrors(['title']);
});
test('validates_special_characters', function () {
$specialChars = '<script>alert("XSS")</script>';
$response = $this->postJson('/api/todos', ['title' => $specialChars])
->assertStatus(201);
// 確保特殊字符被正確處理
expect($response->json('data.title'))->toBe($specialChars);
});
test('validates_unicode_characters', function () {
$unicodeTitle = '測試 📝 テスト 🎯';
$this->postJson('/api/todos', ['title' => $unicodeTitle])
->assertStatus(201)
->assertJsonPath('data.title', $unicodeTitle);
});
<?php
namespace Tests\Feature\Day28;
use App\Models\Todo;
test('api_response_time_is_acceptable', function () {
Todo::factory(10)->create();
$start = microtime(true);
$this->getJson('/api/todos')->assertOk();
$duration = (microtime(true) - $start) * 1000;
expect($duration)->toBeLessThan(100); // 小於 100ms
});
test('handles_concurrent_requests', function () {
$responses = [];
for ($i = 0; $i < 5; $i++) {
$responses[] = $this->postJson('/api/todos', [
'title' => "Concurrent Todo {$i}"
]);
}
foreach ($responses as $response) {
$response->assertStatus(201);
}
$this->assertDatabaseCount('todos', 5);
});
<?php
namespace Tests\Feature\Day28;
use Illuminate\Support\Facades\DB;
test('recovers_from_database_connection_failure', function () {
// 暫時斷開資料庫連接
DB::disconnect();
$response = $this->getJson('/api/health');
$response->assertStatus(503);
// 重新連接
DB::reconnect();
$response = $this->getJson('/api/health');
$response->assertOk();
});
test('handles_malformed_json_requests', function () {
$response = $this->call('POST', '/api/todos', [], [], [], [
'HTTP_CONTENT_TYPE' => 'application/json'
], '{invalid json}');
$response->assertStatus(400)
->assertJsonFragment(['message' => 'Invalid JSON']);
});
<?php
namespace App\Services;
class IntegrationChecker
{
private array $checks = [];
public function checkDatabase(): bool
{
try {
\DB::connection()->getPdo();
$this->checks['database'] = true;
return true;
} catch (\Exception $e) {
$this->checks['database'] = false;
return false;
}
}
public function checkCache(): bool
{
try {
cache(['test' => 'value'], 1);
$result = cache('test') === 'value';
$this->checks['cache'] = $result;
return $result;
} catch (\Exception $e) {
$this->checks['cache'] = false;
return false;
}
}
public function getAllChecks(): array
{
return $this->checks;
}
}
✅ 建立完整的 API 端點健康檢查
✅ 設定並測試 CORS 配置
✅ 準備認證與授權基礎設施
✅ 實作資料驗證邊界測試
✅ 建立效能基準測試
✅ 測試錯誤恢復機制
✅ 驗證 API 文件完整性
今天我們為前後端整合做了充分的準備:
明天我們將把這些準備工作付諸實踐,完成前後端的整合測試!🚀