# HTTP 测试
- [简介](#简介)
- [创建请求](#创建请求)
- [自定义请求头](#customizing-request-headers)
- [Cookies](#cookies)
- [会话 / 认证](#session-and-authentication)
- [调试响应](#debugging-responses)
- [异常处理](#exception-handling)
- [测试 JSON APIs](#testing-json-apis)
- [流畅 JSON 测试](#fluent-json-testing)
- [测试文件上传](#testing-file-uploads)
- [测试视图](#testing-views)
- [渲染切面 & 组件](#rendering-blade-and-components)
- [可用断言](#available-assertions)
- [回应断言](#response-assertions)
- [身份验证断言](#authentication-assertions)
## 简介
Laravel 提供了一个非常流畅的 API,用于向应用程序发出 HTTP 请求并检查响应。例如,看看下面定义的特性测试:
get('/');
$response->assertStatus(200);
}
}
`get`方法向应用程序发出`Get`请求,而`assertStatus`方法则断言返回的响应应该具有给定的 HTTP 状态代码。除了这个简单的断言之外,Laravel 还包含各种用于检查响应头、内容、JSON 结构等的断言。
## 创建请求
要向应用程序发出请求,可以在测试中调用`get`、`post`、`put`、`patch`或`delete`方法。这些方法实际上不会向应用程序发出“真正的”HTTP 请求。相反,整个网络请求是在内部模拟的。
测试请求方法不返回`Illuminate\Http\Response`实例,而是返回`Illuminate\Testing\TestResponse`实例,该实例提供[各种有用的断言](##available-assertions),允许你检查应用程序的响应:
get('/');
$response->assertStatus(200);
}
}
通常,你的每个测试应该只向你的应用发出一个请求。如果在单个测试方法中执行多个请求,则可能会出现意外行为。
> 技巧:为了方便起见,运行测试时会自动禁用 CSRF 中间件。
### 自定义请求头
你可以使用此 `withHeaders` 方法自定义请求的标头,然后再将其发送到应用程序。这使你可以将任何想要的自定义标头添加到请求中:
withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
$response->assertStatus(201);
}
}
### Cookies
在发送请求前你可以使用 `withCookie` 或 `withCookies` 方法设置 cookie。`withCookie` 接受 cookie 的名称和值这两个参数,而 `withCookies` 方法接受一个名称 / 值对数组:
withCookie('color', 'blue')->get('/');
$response = $this->withCookies([
'color' => 'blue',
'name' => 'Taylor',
])->get('/');
}
}
### Session / Authentication
Laravel 提供了几个可在 HTTP 测试时使用 Session 的辅助函数。首先,你需要传递一个数组给 `withSession` 方法来设置 session 数据。这样在应用程序的测试请求发送之前,就会先去给数据加载 session:
withSession(['banned' => false])->get('/');
}
}
Laravel 的 session 通常用于维护当前已验证用户的状态。因此,`actingAs` 方法提供了一种将给定用户作为当前用户进行身份验证的便捷方法。例如,我们可以使用 [工厂模式](/docs/laravel/9.x/database-testing#writing-factories) 生成并验证用户:
create();
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
}
}
你也可以通过传递看守器名称作为 `actingAs` 方法的第二参数以指定用户通过哪种看守器来认证:
$this->actingAs($user, 'web')
### 调试响应
在向你的应用程序发出测试请求之后,可以使用 `dump`、`dumpHeaders` 和 `dumpSession` 方法来检查和调试响应内容:
get('/');
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
}
}
或者,你可以使用 `dd`、`ddHeaders` 和 `ddSession` 方法转储有关响应的信息,然后停止执行:
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`,并断言返回的期望数据:
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` 方法:
postJson('/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
}
}
#### 验证 JSON 路径
如果你想验证 JSON 响应是否包含指定路径上的某些给定数据,可以使用 `assertJsonPath` 方法:
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` 方法相结合,大幅度简化了文件上传测试。举个例子,你可以结合这两者的功能非常方便地进行头像上传表单测试:
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` 的实例,它提供了几个方法来方便地断言视图的内容:
view('welcome', ['name' => 'Taylor']);
$view->assertSee('Taylor');
}
}
`TestView` 对象提供了以下断言方法:`assertSee`、`assertSeeInOrder`、`assertSeeText`、`assertSeeTextInOrder`、`assertDontSee` 和 `assertDontSeeText`。
如果需要,你可以通过将 `TestView` 实例转换为一个字符串获得原始的视图内容:
$contents = (string) $this->view('welcome');
#### 共享错误
一些视图可能依赖于 Laravel 提供的 [全局错误包](/docs/laravel/9.x/validation#quick-displaying-the-validation-errors) 中共享的错误。要在错误包中生成错误消息,可以使用 `withViewErrors` 方法:
$view = $this->withViewErrors([
'name' => ['Please provide a valid name.']
])->view('form');
$view->assertSee('Please provide a valid name.');
### 渲染模板 & 组件
必要的话,你可以使用 `blade` 方法来计算和呈现原始的 [Blade](/docs/laravel/9.x/blade) 字符串。与 `view` 方法一样,`blade` 方法返回的是 `Illuminate\Testing\TestView` 的实例:
$view = $this->blade(
'