在 Laravel 中基于 PHPUnit 进行代码测试:HTTP 测试篇(下)
我们继续在上一篇教程的基础上演示如何编写 HTTP 功能测试用例。
测试用户认证
Session 除了用于存储一次性验证错误信息和用户会话状态外,最主要的用途就是维护用户认证的状态,在 Laravel 中,可以通过框架提供的一系列断言方法对用户认证状态进行测试。
我们基于 Laravel 框架提供的认证脚手架中的路由进行测试,如果你还没有在测试项目中生成认证脚手架代码,运行如下 Artisan 命令生成:
php artisan make:auth
这样,在 routes/web.php
中就会多出如下路由定义代码:
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Auth::routes()
中包含了用户注册、登录相关的路由。
然后我们来编写用户注册登录相关的测试用例,对用户注册、登录功能进行测试,由于涉及到多个测试用例,我们编写一个新的功能测试类:
php artisan make:test AuthTest
该命令会在 tests/Feature
目录下创建一个 AuthTest
类,我们在这个测试类中编写一系列测试用例如下:
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
class AuthTest extends TestCase
{
/**
* 测试注册表单页
*/
public function testRegisterForm()
{
$response = $this->get('/register');
$response->assertOk();
}
/**
* 测试用户提交注册表单
*/
public function testPostRegister()
{
$response = $this->post('/register', [
'name' => 'test',
'email' => 'test@laravel58.test',
'password' => 'password', // Laravel 5.8 验证表单要求密码长度不低于 8 位
'password_confirmation' => 'password'
]);
$response->assertRedirect('/');
}
/**
* 测试登录表单页
*/
public function testLoginForm()
{
$response = $this->get('/login');
$response->assertSuccessful();
}
/**
* 测试用户提交登录表单
*/
public function testPostLogin()
{
$user = User::where('name', 'test')->first();
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);
$response->assertRedirect('/home'); // 用户登录成功后跳转到 /home
}
/**
* 测试未认证状态下访问 /home 路由
*/
public function testHomeWithoutAuthenticated()
{
$response = $this->get('/home');
$response->assertRedirect('/login'); // 用户未认证则跳转到登录页
$this->assertGuest(); // 断言用户未认证
// 断言给定认证凭证是否匹配
$this->assertCredentials([
'email' => 'test@laravel58.test',
'password' => 'password'
]);
}
/**
* 测试认证状态下访问 /home 路由
*/
public function testHomeWithAuthenticated()
{
$user = User::where('name', 'test')->first();
$this->actingAs($user)->get('/home');
$this->assertAuthenticated('web')
->assertAuthenticatedAs($user); // 断言用户认证且以 $user 身份认证
}
}
在认证用户时,我们可以通过模拟提交表单请求或者直接通过 Laravel 提供的 actionAs
方法,使用 actionAs
方法进行模拟认证时,可以通过以下方法断言用户认证:
assertAuthenticated
:断言用户是否认证,默认参数为guard=web
,你可以指定其他的guard
,与之相对的断言方法是assertGuest
,同样,你可以传参指定认证guard
;assertAuthenticatedAs
:断言用户是否以指定用户实例认证,你可以通过第二个参数指定相应的guard
参数;
此外,还可以通过 assertCredentials
方法断言给定认证凭证是否与数据库记录匹配。
我们在命令行中运行 phpunit
运行上述测试用例:
./vendor/bin/phpunit tests/Feature/AuthTest.php
运行结果如下,测试通过:
测试文件上传
测试文件上传的时候,不需要真的上传一个文件,因为这个操作可能会非常耗时,取而代之地,我们可以通过 Illuminate\Http\UploadedFile
类提供的 fake
方法「伪造」文件用于测试,此外,Storage
门面也提供了 fake
方法用于「伪造」文件目录,结合这两个伪造功能,我们可以在 Laravel 框架中轻松实现文件上传测试:
public function testFileUpload()
{
Storage::fake('photos'); // 伪造目录
$photo = UploadedFile::fake()->image('picture.jpg'); // 伪造上传图片
$this->post('/photo', [
'photo' => $photo
]);
Storage::disk('photos')->assertMissing('picture.jpg'); // 断言文件是否上传成功
}
为了让测试用例通过,需要在 routes/web.php
中定义一个路由:
Route::post('/photo', function (\Illuminate\Http\Request $request){
$request->validate([
'photo' => 'required|image|mimes:jpeg,bmp,png,gif'
]);
$photo = $request->file('photo');
$basename = $photo->getClientOriginalName();
return $photo->storeAs('photos', $basename);
});
需要注意的是,通过 Storage::fake
方法伪造的 photos
目录位于 storage/framework/testing/disks/photos
而不是 storage/app/photos
,所以上述测试用例会通过。与之相对的,如果要断言指定文件存在,可以使用 Storage::disk('photos')->assertExists
方法。
除了图片之外,你还可以通过 UploadedFile::fake()->create()
方法伪造其他类型的文件。
测试视图模板
Laravel 框架还提供了对视图模板进行测试的断言方法,以首页 /
路由为例:
Route::get('/', function () {
return view('welcome', ['website' => 'Laravel', 'author' => '学院君']);
});
其对应的视图名称是 welcome
,对应视图模板文件位于 resources/views/welcome.blade.php
,接下来,我们来编写一个测试用例测试该视图:
public function testView()
{
$response = $this->get('/');
$response->assertSeeText('Laravel')
->assertViewHas('website', 'Laravel')
->assertViewMissing('name')
->assertViewHasAll(['website', 'author' => '学院君'])
->assertViewIs('welcome');
}
我们可以通过 assertViewHas
方法断言传递给视图的变量中是否包含某个变量,也可以通过 assertViewHasAll
方法一次性断言多个传递给视图的变量,变量值可以指定,也可以不指定;与之相对的,还可以通过 assertViewMissing
方法断言传递给视图的变量中不包含某个变量。
最后,还可以通过 assertViewIs
方法断言视图名称是否与指定值匹配,该名称即控制器或路由闭包中传递到 view
方法的第一个参数值。
测试 JSON API
HTTP 响应除了返回 Web 视图之外,还可以返回 API 接口,而目前 API 接口的数据格式又以 JSON 为主,下面我们就以 JSON API 为例演示如何对 API 接口编写测试用例。
我们在 routes/api.php
中定义一个测试路由 /api/test
:
Route::get('test/{id}', function ($id) {
$user = \App\User::find($id);
return [
'site' => 'Laravel学院',
'creator' => '学院君',
'user' => $user
];
});
Laravel 框架为所有 JSON API 请求方法提供了支持,你可以通过 json
方法的第一个参数来指定 HTTP 请求方法,比如 GET
、POST
、PUT
、DELETE
等,也可以通过形如 getJson
、postJson
、putJson
、deleteJson
这些方法,这样,就不需要传递请求方法参数了。
以上面这个 API 路由为例,我们可以这样为其编写测试用例:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ApiTest extends TestCase
{
/**
* 测试 JSON API 响应
*
* @return void
*/
public function testJsonApi()
{
$response = $this->json('GET', '/api/test/1');
$response->assertStatus(200)
->assertJson([
'site' => 'Laravel学院',
'creator' => '学院君'
])
->assertJsonMissing([
'name' => '测试用户'
])
->assertJsonCount(6, 'user')
->assertJsonFragment([
'site' => 'Laravel学院'
]);
}
}
在这个测试用例中,我们首先通过 assertStatus
方法断言接口响应状态码是否是200,然后通过 assertJson
方法断言返回 JSON 数据中是否包含给定数据,与之相对的的是 assertJsonMissing
方法,用于断言返回 JSON 数据中不包含给定键。然后,通过 assertJsonCount
方法断言给定键值下数据项的个数,以及 assertJsonFragment
方法断言 JSON 数据中是否包含给定片段,该方法和 assertJson
方法类似。
运行上述测试用例:
./vendor/bin/phpunit tests/Feature/ApiTest.php
结果如下:
除此之外,Laravel 框架还提供了以下断言方法对 JSON 响应数据进行测试:
assertJsonStructure
:断言返回 JSON 响应是否包含给定的数据结构;assertExactJson
:断言返回 JSON 响应是否与期望数据完全一致;assertJsonMissingExact
:断言返回 JSON 响应是否包含给定的完整 JSON 片段;assertJsonValidationErrors
:断言 JSON 响应包含给定键对应的 JSON 格式验证错误信息;assertJsonMissingValidationErrors
:与assertJsonValidationErrors
方法相对。
好了,关于 HTTP 功能测试学院君就简单介绍到这里,下一篇开始我们将会基于 Laravel Dusk 对模拟浏览器进行测试,该功能与 HTTP 测试的区别是可以模拟客户端行为,比如点击、下拉、刷新、键盘输入等。
No Comments