HTTP 测试
简介
Laravel 提供了一个非常流畅的 API,用于向应用程序发出 HTTP 请求并检查响应。例如,看看下面定义的特性测试:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 基本的测试示例.
*
* @return void
*/
public function test_a_basic_request()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
get
方法向应用程序发出Get
请求,而assertStatus
方法则断言返回的响应应该具有给定的 HTTP 状态代码。除了这个简单的断言之外,Laravel 还包含各种用于检查响应头、内容、JSON 结构等的断言。
创建请求
要向应用程序发出请求,可以在测试中调用get
、post
、put
、patch
或delete
方法。这些方法实际上不会向应用程序发出“真正的”HTTP 请求。相反,整个网络请求是在内部模拟的。
测试请求方法不返回Illuminate\Http\Response
实例,而是返回Illuminate\Testing\TestResponse
实例,该实例提供各种有用的断言,允许你检查应用程序的响应:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 基本的测试示例.
*
* @return void
*/
public function test_a_basic_request()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
通常,你的每个测试应该只向你的应用发出一个请求。如果在单个测试方法中执行多个请求,则可能会出现意外行为。
技巧:为了方便起见,运行测试时会自动禁用 CSRF 中间件。
自定义请求头
你可以使用此 withHeaders
方法自定义请求的标头,然后再将其发送到应用程序。这使你可以将任何想要的自定义标头添加到请求中:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 一个基本的功能测试示例
*
* @return void
*/
public function test_interacting_with_headers()
{
$response = $this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
$response->assertStatus(201);
}
}
Cookies
在发送请求前你可以使用 withCookie
或 withCookies
方法设置 cookie。withCookie
接受 cookie 的名称和值这两个参数,而 withCookies
方法接受一个名称 / 值对数组:
<?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:
<?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
方法提供了一种将给定用户作为当前用户进行身份验证的便捷方法。例如,我们可以使用 工厂模式 生成并验证用户:
<?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, 'web')
调试响应
在向你的应用程序发出测试请求之后,可以使用 dump
、dumpHeaders
和 dumpSession
方法来检查和调试响应内容:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 一个最基础的测试例子
*
* @return void
*/
public function test_basic_test()
{
$response = $this->get('/');
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
}
}
或者,你可以使用 dd
、ddHeaders
和 ddSession
方法转储有关响应的信息,然后停止执行:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 基本功能测试例子
*
* @return void
*/
public function test_basic_test()
{
$response = $this->get('/');
$response->ddHeaders();
$response->ddSession();
$response->dd();
}
}
异常处理
有时你可能想要测试你的应用程序是否引发了特定异常。为了确保异常不会被 Laravel 的异常处理程序捕获并作为 HTTP 响应返回,可以在发出请求之前调用 withoutExceptionHandling
方法:
$response = $this->withoutExceptionHandling()->get('/');
此外,如果想确保你的应用程序没有使用 PHP 语言或你的应用程序正在使用的库已弃用的功能,你可以在发出请求之前调用 withoutDeprecationHandling
方法。禁用弃用处理时,弃用警告将转换为异常,从而导致你的测试失败:
$response = $this->withoutDeprecationHandling()->get('/');
测试 JSON APIs
Laravel 也提供了几个辅助函数来测试 JSON APIs 和其响应。例如,json
、getJson
、postJson
、putJson
、patchJson
、deleteJson
以及 optionsJson
可以被用于发送各种 HTTP 动作。你也可以轻松地将数据和请求头传递到这些方法中。首先,让我们实现一个测试示例,发送 POST
请求到 /api/user
,并断言返回的期望数据:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 基本功能测试示例
*
* @return void
*/
public function test_making_an_api_request()
{
$response = $this->postJson('/api/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
}
}
此外,JSON 响应数据可以作为响应上的数组变量进行访问,从而使你可以方便地检查 JSON 响应中返回的各个值:
$this->assertTrue($response['created']);
技巧:assertJson
方法将响应转换为数组,并利用 PHPUnit::assertArraySubset
验证给定数组是否存在于应用程序返回的 JSON 响应中。因此,如果 JSON 响应中还有其他属性,则只要存在给定的片段,此测试仍将通过。
验证 JSON 完全匹配
如前所述,assertJson
方法可用于断言 JSON 响应中存在 JSON 片段。如果你想验证给定数组是否与应用程序返回的 JSON 完全匹配,则应使用 assertExactJson
方法:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 一个基本的功能测试示例。
*
* @return void
*/
public function test_asserting_an_exact_json_match()
{
$response = $this->postJson('/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
}
}
验证 JSON 路径
如果你想验证 JSON 响应是否包含指定路径上的某些给定数据,可以使用 assertJsonPath
方法:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* 一个基本的功能测试示例。
*
* @return void
*/
public function test_asserting_a_json_paths_value()
{
$response = $this->postJson('/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJsonPath('team.owner.name', 'Darian');
}
}
JSON 流式测试
Laravel 还提供了一种漂亮的方式来流畅地测试应用程序的 JSON 响应。首先,将闭包传递给 assertJson
方法。这个闭包将使用 Illuminate\Testing\Fluent\AssertableJson
的实例调用,该实例可用于对应用程序返回的 JSON 进行断言。 where
方法可用于对 JSON 的特定属性进行断言,而 missing
方法可用于断言 JSON 中缺少特定属性:
use Illuminate\Testing\Fluent\AssertableJson;
/**
* 一个基本的功能测试示例。
*
* @return void
*/
public function test_fluent_json()
{
$response = $this->getJson('/users/1');
$response
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
);
}
了解 etc
方法
在上面的示例中,你可能已经注意到我们在断言链的末尾调用了 etc
方法。该方法通知 Laravel 在 JSON 对象上可能存在其他属性。如果未使用 etc
方法,则如果 JSON 对象上存在你未对其进行断言的其他属性,则测试将失败。
此行为背后的目的是通过强制你明确对属性做出断言或通过 etc
方法明确允许其他属性来保护你避免无意中在 JSON 响应中暴露敏感信息。
断言属性存在/不存在
要断言属性存在或不存在,可以使用 has
和 missing
方法:
$response->assertJson(fn (AssertableJson $json) =>
$json->has('data')
->missing('message')
);
此外,hasAll
和 missingAll
方法允许同时断言多个属性的存在或不存在:
$response->assertJson(fn (AssertableJson $json) =>
$json->hasAll('status', 'data')
->missingAll('message', 'code')
);
你可以使用 hasAny
方法来确定是否存在给定属性列表中的至少一个:
$response->assertJson(fn (AssertableJson $json) =>
$json->has('status')
->hasAny('data', 'message', 'code')
);
断言反对 JSON 集合
通常,你的路由将返回一个 JSON 响应,其中包含多个项目,例如多个用户:
Route::get('/users', function () {
return User::all();
});
在这些情况下,我们可以使用 fluent JSON 对象的 has
方法对响应中包含的用户进行断言。例如,让我们断言 JSON 响应包含三个用户。接下来,我们将使用 first
方法对集合中的第一个用户进行一些断言。 first
方法接受一个闭包,该闭包接收另一个可断言的 JSON 字符串,我们可以使用它来对 JSON 集合中的第一个对象进行断言:
$response
->assertJson(fn (AssertableJson $json) =>
$json->has(3)
->first(fn ($json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
)
);
JSON 集合范围断言
有时,你的应用程序的路由将返回分配有命名键的 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()
)
);
但是,你可以进行一次调用,提供一个闭包作为其第三个参数,而不是对 has
方法进行两次单独调用来断言 users
集合。这样做时,将自动调用闭包并将其范围限定为集合中的第一项:
$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'
])
);
你可以使用 |
字符指定多种类型,或者将类型数组作为第二个参数传递给 whereType
方法。如果响应值为任何列出的类型,则断言将成功:
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('name', 'string|null')
->whereType('id', ['string', 'integer'])
);
whereType
和 whereAllType
方法识别以下类型:string
、integer
、double
、boolean
、array
和 null
。
测试文件上传
Illuminate\Http\UploadedFile
提供了一个 fake
方法用于生成虚拟的文件或者图像以供测试之用。它可以和 Storage
facade 的 fake
方法相结合,大幅度简化了文件上传测试。举个例子,你可以结合这两者的功能非常方便地进行头像上传表单测试:
<?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()
{
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->post('/avatar', [
'avatar' => $file,
]);
Storage::disk('avatars')->assertExists($file->hashName());
}
}
如果你想断言一个给定的文件不存在,则可以使用由 Storage
facade 提供的 AssertMissing
方法:
Storage::fake('avatars');
// ...
Storage::disk('avatars')->assertMissing('missing.jpg');
虚拟文件定制
在使用 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
的实例,它提供了几个方法来方便地断言视图的内容:
<?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' => ['Please provide a valid name.']
])->view('form');
$view->assertSee('Please provide a valid name.');
渲染模板 & 组件
必要的话,你可以使用 blade
方法来计算和呈现原始的 Blade 字符串。与 view
方法一样,blade
方法返回的是 Illuminate\Testing\TestView
的实例:
$view = $this->blade(
'<x-component :name="$name" />',
['name' => 'Taylor']
);
$view->assertSee('Taylor');
你可以使用 component
方法来评估和渲染 Blade 组件。类似于 view
方法,component
方法返回一个 Illuminate\Testing\TestView
的实例:
$view = $this->component(Profile::class, ['name' => 'Taylor']);
$view->assertSee('Taylor');
可用断言
响应断言
Laravel 的 Illuminate \ Testing \ TestResponse
类提供了各种自定义断言方法,你可以在测试应用程序时使用它们。可以在由 json
、get
、post
、put
和 delete
方法返回的响应上访问这些断言:
assertCookie assertCookieExpired assertCookieNotExpired assertCookieMissing assertCreated assertDontSee assertDontSeeText assertDownload assertExactJson assertForbidden assertHeader assertHeaderMissing assertJson assertJsonCount assertJsonFragment assertJsonMissing assertJsonMissingExact assertJsonMissingValidationErrors assertJsonPath assertJsonStructure assertJsonValidationErrors assertJsonValidationErrorFor assertLocation assertNoContent assertNotFound assertOk assertPlainCookie assertRedirect assertRedirectContains assertRedirectToSignedRoute assertSee assertSeeInOrder assertSeeText assertSeeTextInOrder assertSessionHas assertSessionHasInput assertSessionHasAll assertSessionHasErrors assertSessionHasErrorsIn assertSessionHasNoErrors assertSessionDoesntHaveErrors assertSessionMissing assertSimilarJson assertStatus assertSuccessful assertUnauthorized assertUnprocessable assertValid assertInvalid assertViewHas assertViewHasAll assertViewIs assertViewMissing
assertCookie
断言响应中包含给定的 cookie:
$response->assertCookie($cookieName, $value = null);
assertCookieExpired
断言响应包含给定的过期的 cookie:
$response->assertCookieExpired($cookieName);
assertCookieNotExpired
断言响应包含给定的未过期的 cookie:
$response->assertCookieNotExpired($cookieName);
assertCookieMissing
断言响应不包含给定的 cookie:
$response->assertCookieMissing($cookieName);