資料庫測試
簡介
Laravel 提供了數種實用工具與 Assertion (判斷提示) 讓你能更輕鬆地測試由資料庫驅動的網站。此外,通過 Laravel 的 Model Factory 與 Seeder,也能輕鬆地使用專案的 Eloquent Model 與 Eloquent 關聯來測試資料庫。我們會在接下來的說明文件內討論這些強大的工具。
Resetting the Database After Each Test
在進一步繼續之前,我們先來討論如何在每個測試前重設資料庫,這樣一來前一個測試的資料就不會影響到接下來的測試。Laravel 內含了 Illuminate\Foundation\Testing\RefreshDatabase
Trait,會處理這樣的重設。只需要在測試類別內 use 這個 Trait 即可:
1<?php23use Illuminate\Foundation\Testing\RefreshDatabase;45uses(RefreshDatabase::class);67test('basic example', function () {8 $response = $this->get('/');910 // ...11});
1<?php23use Illuminate\Foundation\Testing\RefreshDatabase;45uses(RefreshDatabase::class);67test('basic example', function () {8 $response = $this->get('/');910 // ...11});
1<?php23namespace Tests\Feature;45use Illuminate\Foundation\Testing\RefreshDatabase;6use Tests\TestCase;78class ExampleTest extends TestCase9{10 use RefreshDatabase;1112 /**13 * A basic functional test example.14 */15 public function test_basic_example(): void16 {17 $response = $this->get('/');1819 // ...20 }21}
1<?php23namespace Tests\Feature;45use Illuminate\Foundation\Testing\RefreshDatabase;6use Tests\TestCase;78class ExampleTest extends TestCase9{10 use RefreshDatabase;1112 /**13 * A basic functional test example.14 */15 public function test_basic_example(): void16 {17 $response = $this->get('/');1819 // ...20 }21}
當資料庫架構 (Schema) 已是最新的時候, Illuminate\Foundation\Testing\RefreshDatabase
Trait 將不會執行資料庫遷移 (Migration),只會在資料庫 Transaction 中執行測試。因此,在未使用該 Trait 中的測試例中,若有新增紀錄,將會保留在資料庫中。
若想完整重設資料庫,請改用 Illuminate\Foundation\Testing\DatabaseMigrations
或 Illuminate\Foundation\Testing\DatabaseTruncation
Trait。不過,這兩種方式都會比 RefreshDatabase
Trait 慢很多。
Model Factory
在測試時,我們可能會需要在執行測試前先插入一些資料到資料庫內。比起在建立這個測試資料時手動指定各個欄位的值,在 Laravel 中,我們可以使用 [Model Factory]((/docs/11.x/eloquent-factories) 來為各個 Eloquent Model 定義一系列的預設屬性。
若要瞭解更多有關建立 Model Factory,或是使用 Model Factory 來建立 Model 的資訊,請參考完整的 Model Factory 說明文件。定義好 Model Factory 後,就可以在測試中使用 Factory 來建立 Model:
1use App\Models\User;23test('models can be instantiated', function () {4 $user = User::factory()->create();56 // ...7});
1use App\Models\User;23test('models can be instantiated', function () {4 $user = User::factory()->create();56 // ...7});
1use App\Models\User;23public function test_models_can_be_instantiated(): void4{5 $user = User::factory()->create();67 // ...8}
1use App\Models\User;23public function test_models_can_be_instantiated(): void4{5 $user = User::factory()->create();67 // ...8}
執行 Seeder
若想使用資料庫 Seeder 來在功能測試時修改資料庫,則可以叫用 seed
方法。預設情況下,seed
方法會執行 DatabaseSeeder
,該 Seeder 應用來執行所有其他的 Seeder。或者,也可以傳入指定的 Seeder 類別名稱給 seed
方法:
1<?php23use Database\Seeders\OrderStatusSeeder;4use Database\Seeders\TransactionStatusSeeder;5use Illuminate\Foundation\Testing\RefreshDatabase;67uses(RefreshDatabase::class);89test('orders can be created', function () {10 // Run the DatabaseSeeder...11 $this->seed();1213 // Run a specific seeder...14 $this->seed(OrderStatusSeeder::class);1516 // ...1718 // Run an array of specific seeders...19 $this->seed([20 OrderStatusSeeder::class,21 TransactionStatusSeeder::class,22 // ...23 ]);24});
1<?php23use Database\Seeders\OrderStatusSeeder;4use Database\Seeders\TransactionStatusSeeder;5use Illuminate\Foundation\Testing\RefreshDatabase;67uses(RefreshDatabase::class);89test('orders can be created', function () {10 // Run the DatabaseSeeder...11 $this->seed();1213 // Run a specific seeder...14 $this->seed(OrderStatusSeeder::class);1516 // ...1718 // Run an array of specific seeders...19 $this->seed([20 OrderStatusSeeder::class,21 TransactionStatusSeeder::class,22 // ...23 ]);24});
1<?php23namespace Tests\Feature;45use Database\Seeders\OrderStatusSeeder;6use Database\Seeders\TransactionStatusSeeder;7use Illuminate\Foundation\Testing\RefreshDatabase;8use Tests\TestCase;910class ExampleTest extends TestCase11{12 use RefreshDatabase;1314 /**15 * Test creating a new order.16 */17 public function test_orders_can_be_created(): void18 {19 // Run the DatabaseSeeder...20 $this->seed();2122 // Run a specific seeder...23 $this->seed(OrderStatusSeeder::class);2425 // ...2627 // Run an array of specific seeders...28 $this->seed([29 OrderStatusSeeder::class,30 TransactionStatusSeeder::class,31 // ...32 ]);33 }34}
1<?php23namespace Tests\Feature;45use Database\Seeders\OrderStatusSeeder;6use Database\Seeders\TransactionStatusSeeder;7use Illuminate\Foundation\Testing\RefreshDatabase;8use Tests\TestCase;910class ExampleTest extends TestCase11{12 use RefreshDatabase;1314 /**15 * Test creating a new order.16 */17 public function test_orders_can_be_created(): void18 {19 // Run the DatabaseSeeder...20 $this->seed();2122 // Run a specific seeder...23 $this->seed(OrderStatusSeeder::class);2425 // ...2627 // Run an array of specific seeders...28 $this->seed([29 OrderStatusSeeder::class,30 TransactionStatusSeeder::class,31 // ...32 ]);33 }34}
或者,也可以使用 RefreshDatabase
Trait 來讓 Laravel 在每次測試前都自動執行資料庫 Seed。可以通過在基礎測試類別上定義 $seed
屬性來完成:
1<?php23namespace Tests;45use Illuminate\Foundation\Testing\TestCase as BaseTestCase;67abstract class TestCase extends BaseTestCase8{9 /**10 * Indicates whether the default seeder should run before each test.11 *12 * @var bool13 */14 protected $seed = true;15}
1<?php23namespace Tests;45use Illuminate\Foundation\Testing\TestCase as BaseTestCase;67abstract class TestCase extends BaseTestCase8{9 /**10 * Indicates whether the default seeder should run before each test.11 *12 * @var bool13 */14 protected $seed = true;15}
當 $seed
屬性為 true
時,各個使用了 RefreshDatabase
Trait 的測試都會在開始前先執行 Database\Seeders\DatabaseSeeder
類別。不過,也可以通過在測試類別內定義 $seeder
屬性來指定要執行的 Seeder。
1use Database\Seeders\OrderStatusSeeder;23/**4 * Run a specific seeder before each test.5 *6 * @var string7 */8protected $seeder = OrderStatusSeeder::class;
1use Database\Seeders\OrderStatusSeeder;23/**4 * Run a specific seeder before each test.5 *6 * @var string7 */8protected $seeder = OrderStatusSeeder::class;
可用的 Assertion
Laravel provides several database assertions for your Pest or PHPUnit feature tests. We'll discuss each of these assertions below.
assertDatabaseCount
判斷資料庫中的某個資料表是否包含給定數量的記錄:
1$this->assertDatabaseCount('users', 5);
1$this->assertDatabaseCount('users', 5);
assertDatabaseEmpty
Assert that a table in the database contains no records:
1$this->assertDatabaseEmpty('users');
1$this->assertDatabaseEmpty('users');
assertDatabaseHas
判斷資料庫中的某個資料表包含符合給定索引鍵/值查詢條件的記錄:
1$this->assertDatabaseHas('users', [3]);
1$this->assertDatabaseHas('users', [3]);
assertDatabaseMissing
判斷資料庫中的某個資料表是否不包含符合給定索引鍵/值查詢條件的記錄:
1$this->assertDatabaseMissing('users', [3]);
1$this->assertDatabaseMissing('users', [3]);
assertSoftDeleted
assertSoftDeleted
方法可用來判斷給定 Eloquent Model 是否已「軟刪除 (Soft Delete)」:
1$this->assertSoftDeleted($user);
1$this->assertSoftDeleted($user);
assertNotSoftDeleted
assertNotSoftDeleted
方法可用來判斷給定 Eloquent Model 是否未被「軟刪除 (Soft Delete)」:
1$this->assertNotSoftDeleted($user);
1$this->assertNotSoftDeleted($user);
assertModelExists
判斷給定 Model 存在資料庫中:
1use App\Models\User;23$user = User::factory()->create();45$this->assertModelExists($user);
1use App\Models\User;23$user = User::factory()->create();45$this->assertModelExists($user);
assertModelMissing
判斷給定 Model 不存在資料庫中:
1use App\Models\User;23$user = User::factory()->create();45$user->delete();67$this->assertModelMissing($user);
1use App\Models\User;23$user = User::factory()->create();45$user->delete();67$this->assertModelMissing($user);
expectsDatabaseQueryCount
可以在測試的最開始呼叫 expectsDatabaseQueryCount
方法來指定在執行此測試的期間預期執行多少筆資料庫查詢。若實際執行的查詢數不符合該預期,測試就會失敗:
1$this->expectsDatabaseQueryCount(5);23// Test...
1$this->expectsDatabaseQueryCount(5);23// Test...