【Laravel官方文件導讀】超完整HTTP測試攻略
簡介
Laravel 提供了一個非常流暢的 API 用來向應用發出 HTTP 請求並檢查其回應。例如下面的這段功能測試:
//tests\Feature\ExampleTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_a_basic_request()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
例子中 get() 向應用發出 GET 請求,而 assertStatus() 則確認返回的回應具有指定的 HTTP 狀態碼。除了這個簡單的確認外,Laravel 還包含用於檢查 Header,內容,JSON 結構等的各種確認工具
建立請求
要為你的應用去建立請求,需要在測試用例內去呼叫 get() . post() . put() . patch() 或 delete() 等方法。實際上這些方法並不是真的去發出 HTTP 請求到應用,僅僅是做內部模擬而已
也就是說,回應的並非 Illuminate\Http\Response 實例,而是 Illuminate\Testing\TestResponse 的實例。它提供了一系列有用的確認工具來允許你去檢查應用的回應
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_a_basic_request()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
而為了方便, CSRF 防護中介層會在執行測試時被自動關閉
自定義請求頭(Request Header)
你可使用 withHeaders() 自定義請求的標頭,然後再將其發送到應用內。這使你可自由的將任何想要的自定義標頭添加到請求中,請看下面這個例子:
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_interacting_with_headers()
{
$response = $this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
//201狀態碼表示成功新增資料
$response->assertStatus(201);
}
}
Cookies
在發送請求前你可以使用 withCookie() 或 withCookies() 設置 cookie。withCookie() 接受 cookie 的名稱和值這兩個參數,而 withCookies() 接受一個名稱 / 值對陣列:
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_interacting_with_cookies()
{
$response = $this->withCookie('color', 'blue')->get('/');
$response = $this->withCookies([
'color' => 'blue',
'name' => 'Taylor',
])->get('/');
}
}
會話與驗證(Session / Authentication)
Laravel 提供了一些幫助函式用於在 HTTP 測試過程中去與 Session 互動。首先,你能夠利用 withSession() 去設定 Session 到指定陣列。這有助於如果需要在請求中去取得 Session 時很有用
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_interacting_with_the_session()
{
$response = $this->withSession(['banned' => false])->get('/');
}
}
Laravel 的對話 (Session) 常用於保存當前登入者的狀態。這時候 actingAs() 提供了一種便利方式讓你以某個用戶作為測試請求登入者。例如下面這個例子,我們使用模型工廠來產生一個新用戶並用其進行登入:
\\tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_an_action_that_requires_authentication()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
}
}
你也可以通過傳入看守器名稱作為 actingAs() 的第二參數以指定用戶通過哪種看守器來認證:
$this->actingAs($user, 'api')
對回應進行除錯
在對應用進行測試請求後,dump() . dumpHeaders() 和 dumpSession() 可以用來分析並針對回應來進行除錯:
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_basic_test()
{
$response = $this->get('/');
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
}
}
測試 JSON 的 API
Laravel 也提供了幾個幫助函式來測試 JSON API 和其回應。例如,json() . getJson() . postJson() . putJson() . patchJson() . deleteJson() 以及 optionsJson() 可以被用於發送各種 HTTP 動詞。你也可以輕鬆地將資料和請求頭傳遞到這些方法中。下面的這個測試用例, 發送 POST 請求到 /user ,並確認返回的期望資料:
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_making_an_api_request()
{
$response = $this->postJson('/api/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
}
}
JSON 回應資料可當作陣列變數來進行取用,方便你去檢查所傳回的某個值
技巧:
assertJson 將回應轉換為一個陣列並利用 PHPUnit::assertArraySubset() 來驗證指定的陣列存在於應用返回的 JSON 回應中。因此如果 JSON 回應中有其他屬性,測試仍舊會在指定陣列存在的情況下通過
$this->assertTrue($response['created']);
確認 JSON 完全匹配
如果你想驗證指定的陣列完全而非部份匹配應用返回的 JSON 結果,可使用 assertExactJson():
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_asserting_an_exact_json_match()
{
$response = $this->json('POST', '/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
}
}
Asserting On JSON Paths If you would like to verify that the JSON response contains the given data at a specified path, you should use the assertJsonPath method:
確認 JSON 路徑
如果你想確認 JSON 回應是否包含指定路徑上的某些指定資料,可以使用 assertJsonPath() :
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_asserting_a_json_paths_value()
{
$response = $this->json('POST', '/user', ['name' => 'Sally']);
$response->assertStatus(201)
->assertJsonPath('team.owner.name', 'Darian');
}
}
更流暢的進行 JSON 測試
Laravel 也提供一個更漂亮的方式來讓你測試應用的回應。首先,傳入一個 Closure 到 assertJson()。這個 Closure 將得到 Illuminate\Testing\Fluent\AssertableJson 的實例用來確認應用所傳回的 JSON
where() 用來確認 JSON 內的指定值是否存在且正確,反之,missing() 用來確認 JSON 不存在該指定值
use Illuminate\Testing\Fluent\AssertableJson;
public function test_fluent_json()
{
$response = $this->json('GET', '/users/1');
$response
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
);
}
關於 etc()
在上面的例子中,你可能注意到有個 etc() 被串在確認鏈的最後頭。這個方法是用來向 Laravel 報告說 JSON 物件裡頭還有其他沒有確認的屬性值。假如你沒有使用 etc(),這個測試會失敗因為你沒有驗證完 JSON 上的所有屬性
Laravel 這樣設計的背後原因是為了保護你,避免在無意間將敏感資料暴露在 JSON 回應裡頭。為此,你的處理策略有兩個:對該屬性進行驗證又或者是乾脆呼叫 etc() 來讓 Laravel 不要管
確認 JSON 集合
在你回傳的 JSON 回應包含多筆資料是很常見的,比如多個用戶:
Route::get('/users', function () {
return User::all();
});
在這種情況下,可使用 JSON 物件的 has() 來確認指定用戶是否存在於回應的集合內。例如下面這個例子,首先確認 JSON 回應包含3筆資料。接下來我們用 first() 來對第一個用戶進行一些確認, first() 接受一個 Closure 並傳入測試用 JSON 字串 ,讓我們能夠使用剛才的技巧來檢查 JSON 集合內的第一筆內容
$response->assertJson(fn (AssertableJson $json) =>
$json->has(3)
->first(fn ($json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
)
);
Scoping JSON Collection Assertions
有時候,你所回傳的 JSON 集合為鍵值對的結構:
Route::get('/users', function () {
return [
'meta' => [...],
'users' => User::all(),
];
})
當驗證這樣的回應,可使用 has() 來檢查集合內某個鍵值所對應的陣列裡頭的元素數量。此外,你也能夠使用 has() 來針對某個區域進行一連串的驗證,請看下面這個例子
$response->assertJson(fn (AssertableJson $json) =>
$json->has('meta')
->has('users', 3)
->has('users.0', fn ($json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
)
);
上面的這個寫法還可以再進行簡化,你可以把後面兩個關於 users 的驗證整合成一個,做法是在 has() 多傳入一個第二參數,內容為要驗證的數量。在這情況下,你不再需要去指定索引值為0的元素,因為它預設就會針對集合內的第一筆資料來進行驗證。下面這個寫法結果和上面完全相同:
$response
->assertJson(fn (AssertableJson $json) =>
$json->has('meta')
->has('users', 3, fn ($json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
)
);
確認 JSON 類型
你可能只想要確認 JSON 回應的某個屬性是否為指定類型。Illuminate\Testing\Fluent\AssertableJson 類別提供了 whereType() 和 whereAllType() 就是為此而生的
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('id', 'integer')
->whereAllType([
'users.0.name' => 'string',
'meta' => 'array'
])
);
如果要驗證多個類型可使用 | 字元,或者是將第二參數改為傳入一個陣列,裡頭放入要驗證的所有類型。在這情況下,只要該回應值的類型屬於陣列內的任何一種就算成功
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('name', 'string|null')
->whereType('id', ['string', 'integer'])
);
whereType() 和 whereTypeAll() 支援的類型:
| 類型 | 說明 |
|---|---|
| string | 字串 |
| integer | 整數 |
| double | 浮點數 |
| boolean | 布林值 |
| array | 陣列 |
| null | 空值 |
測試檔案上傳
Illuminate\Http\UploadedFile 提供了一個 fake() 用於生成虛擬的文件或者圖像以供測試之用。它可以和 Storage facade 的 fake() 相結合,大幅簡化檔案上傳測試。舉個例子,下面結合這兩個功能來進行頭像上傳表單的測試:
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_avatars_can_be_uploaded()
{
//模擬生成 avatars 資料夾
Storage::fake('avatars');
//模擬出一個 avatar.jpg 圖片
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->post('/avatar', [
'avatar' => $file,
]);
// 確認文件有被儲存
Storage::disk('avatars')->assertExists($file->hashName());
// 確認 missing.jpg 文件不存在
Storage::disk('avatars')->assertMissing('missing.jpg');
}
}
虛擬文件自定義
在使用 UploadedFile 類別所提供的 fake() 創建文件時,你可以指定圖片的寬高以及大小,從而更好的確認測試規則:
UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100);
除了創建圖片外,你也可以用 create() 來創建其他類型的文件
UploadedFile::fake()->create('document.pdf', $sizeInKilobytes);
如果需要,你還可以傳入一個 $mimeType 參數來明確定義文件應返回的 MIME 類型
UploadedFile::fake()->create(
'document.pdf', $sizeInKilobytes, 'application/pdf'
);
測試視圖
Laravel 允許你在不向應用程序發出模擬 HTTP 請求的情況下獨立呈現視圖。為此,你可以在測試中使用 view() 。view() 接受視圖名稱和一個可選的資料陣列。這個方法返回一個 Illuminate\Testing\TestView 的實例,它提供了幾個方法來方便地確認視圖的內容
//tests\TestCase\ExampleTest.php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_a_welcome_view_can_be_rendered()
{
$view = $this->view('welcome', ['name' => 'Taylor']);
$view->assertSee('Taylor');
}
}
TestView 物件提供了以下確認方法:assertSee() . assertSeeInOrder() . assertSeeText() . assertSeeTextInOrder() . assertDontSee() 和 assertDontSeeText()
如果需要,你可以通過將 TestView 實例轉型為一個字串來獲得原始的視圖內容
$contents = (string) $this->view('welcome');
共享錯誤
一些視圖可能依賴於 Laravel 提供的全域錯誤包中共享的錯誤。要在錯誤包中生成錯誤訊息,可以使用 withViewErrors():
$view = $this->withViewErrors([
'name' => ['請提供有效的名字']
])->view('form');
$view->assertSee('請提供有效的名字');
渲染原始的 Blade
如有必要,你可以使用 blade() 來計算和呈現原始的 Blade 字串。與 view() 一樣,blade() 返回的是 Illuminate\Testing\TestView 實例
$view = $this->blade(
'<x-component :name="$name" />',
['name' => '哥布林工程師']
);
$view->assertSee('哥布林工程師');
你能夠使用 component() 來驗證與渲染一個 Blade 組件。和 view() 一樣,component() 會回傳 Illuminate\Testing\TestView 實例
$view = $this->component(Profile::class, ['name' => '哥布林工程師']);
$view->assertSee('哥布林工程師');
所支持的確認方法
回應確認
Laravel 的 Illuminate\Testing\TestResponse 為你的功能測試提供了各種自定義確認方法。這些確認可以串在從 json() .get() .post() . put() 和 delete() 等測試方法返回的回應之後:
assertCookie()
確認回應中包含指定的 cookie
$response->assertCookie($cookieName, $value = null);
assertCookieExpired()
確認回應包含指定的已過期 cookie
$response->assertCookieExpired($cookieName);
assertCookieNotExpired()
確認回應包含指定的尚未過期 cookie
$response->assertCookieNotExpired($cookieName);
assertCookieMissing()
確認回應不包含指定的 cookie:
$response->assertCookieMissing($cookieName);
assertCreated()
確認回應的狀態碼為 201:
$response->assertCreated();
assertDontSee()
確認指定的字串不包含在回應中。除非傳遞第二個參數 false,否則此確認將指定字串進行跳脫後再匹配:
$response->assertDontSee($value, $escaped = true);
assertDontSeeText()
確認指定字串不包含在回應文字內容中。除非傳遞第二個參數 false,否則此確認將指定字串進行跳脫後才匹配。另外此方法將會在測試前跳過回應中包含 strip_tags()的內容:
$response->assertDontSeeText($value, $escaped = true);
assertExactJson()
確認回應包含與指定 JSON 資料完全匹配
$response->assertExactJson(array $data);
assertForbidden()
確認回應中有禁止訪問 (403) 狀態碼
$response->assertForbidden();
assertHeader()
確認指定的 header 在回應中存在
$response->assertHeader($headerName, $value = null);
assertHeaderMissing()
確認指定的 header 在回應中不存在
$response->assertHeaderMissing($headerName);
assertJson()
確認回應包含指定的 JSON 資料
$response->assertJson(array $data, $strict = false);
這方法會將回應轉成陣列並執行 PHPUnit::assertArraySubset() 來確認指定陣列存在於 JSON 回應內。因此如果JSON回應中包含其他屬性的話,只要指定陣列有存在就沒關係
assertJsonCount()
確認回應 JSON 中有一個陣列,其中包含指定鍵的預期元素數量
$response->assertJsonCount($count, $key = null);
assertJsonFragment()
確認回應包含指定 JSON 片段
Route::get('/users', function () {
return [
'users' => [
[
'name' => 'Taylor Otwell',
],
],
];
});
$response->assertJsonFragment(['name' => 'Taylor Otwell']);
assertJsonMissing()
確認回應未包含指定的 JSON 片段
$response->assertJsonMissing(array $data);
assertJsonMissingExact()
確認回應不包含確切的 JSON 片段
$response->assertJsonMissingExact(array $data);
assertJsonMissingValidationErrors()
在 Laravel 驗證返回的 JSON 格式的錯誤中缺少指定的鍵
$response->assertJsonMissingValidationErrors($keys);
assertJsonPath()
確認 JSON 回應包含指定節點上的指定資料
$response->assertJsonPath($path, $expectedValue);
例如 JSON 回應包含以下資料:
{
"user": {
"name": "Steve Schoger"
}
}
你可能想要確認 user 物件的 name 屬性符合某個指定值:
$response->assertJsonPath('user.name', '哥布林');
assertJsonStructure()
確認回應具有指定的 JSON 結構
$response->assertJsonStructure(array $structure);
例如,回應包含以下資料:
{
"user": {
"name": "Steve Schoger"
}
}
你能夠像這樣去確認 JSON 結構是否符合你的預期:
$response->assertJsonStructure([
'user' => [
'name',
]
]);
有時, JSON 回應會包含物件陣列:
{
"user": [
{
"name": "Steve Schoger",
"age": 55,
"location": "Earth"
},
{
"name": "Mary Schoger",
"age": 60,
"location": "Earth"
}
]
}
在這情況下,你能使用萬用字元(*) 來驗證在該陣列裡頭的所有物件之結構:
$response->assertJsonStructure([
'user' => [
'*' => [
'name',
'age',
'location'
]
]
]);
assertJsonValidationErrors()
在 Laravel 驗證返回的 JSON 格式的錯誤中包含指定的鍵。此方法只能驗證錯誤包是以 JSON 結構而非存放於 session 的設計
$response->assertJsonValidationErrors(array $data);
assertLocation()
確認回應在 Location Header 中具有指定的 URI 值
$response->assertLocation($uri);
assertNoContent()
確認回應具有指定的狀態碼且沒有內容
$response->assertNoContent($status = 204);
assertNotFound()
確認回應具有未找到狀態碼(404)
$response->assertNotFound();
assertOk()
確認回應有正常狀態碼(200)
$response->assertOk();
assertPlainCookie()
確認回應包含指定的 cookie (未加密)
$response->assertPlainCookie($cookieName, $value = null);
assertRedirect()
確認回應會轉址到指定的 URI
$response->assertRedirect($uri);
assertSee()
確認指定的字串包含在回應中。除非傳遞第二個參數 false ,否則此確認將指定字串進行跳脫後再比對
$response->assertSee($value, $escaped = true);
assertSeeInOrder()
確認指定的字串按順序包含在回應中。除非傳遞第二個參數 false ,否則此確認將指定字串進行跳脫後再比對
$response->assertSeeInOrder(array $values, $escaped = true);
assertSeeText()
確認指定字串包含在回應文字內容中。除非傳遞第二個參數 false,否則此確認將指定字串進行跳脫後再比對,在比對前會跳過 strip_tags()
$response->assertSeeText($value, $escaped = true);
assertSeeTextInOrder()
確認指定的字串按順序包含在回應的文字內容中。除非傳遞第二個參數 false ,否則此確認將指定字串進行跳脫後再比對,在比對前會跳過 strip_tags()
$response->assertSeeTextInOrder(array $values, $escaped = true);
assertSessionHas()
確認 session 包含指定的資料
$response->assertSessionHas($key, $value = null);
assertSessionHasInput()
session 在閃存輸入陣列中確認具有指定值
$response->assertSessionHasInput($key, $value = null);
assertSessionHasAll()
確認 Session 中具有指定的鍵值對列表
$response->assertSessionHasAll(array $data);
例如,應用的 session 包含名字和狀態這兩個鍵,你能夠驗證它們是否存在並是否具備指定值,如下寫法:
$response->assertSessionHasAll([
'name' => 'Taylor Otwell',
'status' => 'active',
]);
assertSessionHasErrors()
確認 session 包含指定 $keys 的 Laravel 驗證錯誤。如果 $keys 是關聯陣列,則確認 session 包含每個欄位(key)的特定錯誤訊息(value)。此方法適用於驗證錯誤是以 session 進行儲存而非以 JSON 結構來回傳的設計
$response->assertSessionHasErrors(
array $keys, $format = null, $errorBag = 'default'
);
例如確認 name 和 email 欄位有驗證錯誤且被快閃存放於 session ,你就能夠呼叫 the assertSessionHasErrors(),像這樣:
$response->assertSessionHasErrors(['name', 'email']);
或者,你也能夠確認指定欄位是否具備特定的驗證錯誤訊息
$response->assertSessionHasErrors([
'name' => 'The given name was invalid.'
]);
assertSessionHasErrorsIn()
功能與前者 assertSessionHasErrors() 大致相同,但針對特定的錯誤包
$response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null);
assertSessionHasNoErrors()
確認 session 中沒有 laravel 驗證錯誤:
$response->assertSessionHasNoErrors();
assertSessionDoesntHaveErrors()
確認 session 缺少指定的錯誤,如果 $key 為空,則確認 session 沒有任何錯誤:
$response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default');
assertSessionMissing()
確認 session 中缺少指定的 $key
$response->assertSessionMissing($key);
assertStatus()
確認回應指定的 http 狀態碼
Assert that the response has a given HTTP status code:
$response->assertStatus($code);
assertSuccessful()
確認回應為成功的狀態碼 (>= 200 且 < 300)
$response->assertSuccessful();
assertUnauthorized()
確認回應為未認證的狀態碼 (401)
$response->assertUnauthorized();
assertViewHas()
確認回應視圖包含了指定鍵值對資料
$response->assertViewHas($key, $value = null);
此外,視圖資料可作為回應上的陣列變數,方便你進行檢查:
$this->assertEquals('哥布林工程師', $response['name']);
assertViewHasAll()
確認回應視圖具有指定的資料列表
$response->assertViewHasAll(array $data);
此方法用於確認該視圖只包含指定鍵的資料
$response->assertViewHasAll([
'name',
'email',
]);
如果你不只想確認指定鍵存在,而要確認值是否正確,可改成這樣:
$response->assertViewHasAll([
'name' => 'Taylor Otwell',
'email' => 'taylor@example.com,',
]);
assertViewIs()
確認當前路由返回的的視圖是指定的視圖
$response->assertViewIs($value);
assertViewMissing()
確認回應的視圖缺少某個鍵的資料
$response->assertViewMissing($key);
驗證確認
Laravel 還為 PHPUnit 提供了各種與身份驗證相關的確認方便你用於功能測試
要注意的是這些方法是透過測試用例本身來進行呼叫而非 get() 和 post() 所回傳的 Illuminate\Testing\TestResponse 實例
assertAuthenticated()
確認當前用戶已通過身份驗證
$this->assertAuthenticated($guard = null);
assertGuest()
確認當前用戶沒有通過身份驗證
$this->assertGuest($guard = null);
assertAuthenticatedAs()
確認指定的用戶已通過身份驗證
$this->assertAuthenticatedAs($user, $guard = null);



