在 Laravel 中基于 PHPUnit 进行代码测试:HTTP 测试篇(上)
底层实现
上一篇教程我们介绍了在 Laravel 框架中如何基于 PHPUnit 编写单元测试,其实单元测试基本上使用的都是 PHPUnit 框架提供的原生方法,今天我们来看下 Laravel 如何基于 PHPUnit 实现 HTTP 功能测试。
Laravel 框架开箱为我们提供了一个功能测试用例示例 tests/Feature/ExampleTest.php
:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
功能测试类都位于 tests/Feature
目录下,和单元测试类一样,也继承自 Tests\TestCase
,从根源上都继承自 PHPUnit\Framework\TestCase
,只不过功能测试用到的很多方法都是 Laravel 自行封装实现的,这些实现都是通过独立的 Trait 来完成,在 Illuminate\Foundation\Testing\TestCase
中,可以看到这些 Trait 的引入:
use Concerns\InteractsWithContainer,
Concerns\MakesHttpRequests,
Concerns\InteractsWithAuthentication,
Concerns\InteractsWithConsole,
Concerns\InteractsWithDatabase,
Concerns\InteractsWithExceptionHandling,
Concerns\InteractsWithSession,
Concerns\MocksApplicationServices;
比如请求相关的测试方法都位于 Illuminate\Foundation\Testing\Concerns\MakesHttpRequests
中,认证相关的测试方法都位于 Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication
中,会话相关的测试方法都位于 Illuminate\Foundation\Testing\Concerns\InteractsWithSession
中,而响应相关的测试方法都位于 Illuminate\Foundation\Testing\TestResponse
中,该实例会在调用 HTTP 功能测试类中调用 $this->get
方法时返回(当然,还支持 post
、put
、delete
、getJson
等类似方法,这些方法都定义在 Illuminate\Foundation\Testing\Concerns\MakesHttpRequests
中)。
接下来,我们就基于 Laravel 提供的这些测试方法对 HTTP 请求和响应进行测试。
基本测试
如 Laravel 提供的示例代码所示,我们可以从最简单的测试开始,测试响应的状态码,与之等价的,我们还可以通过 assertOk
方法断言响应状态码是否是 200:
public function testBasic()
{
$response = $this->get('/');
$response->assertOk(); // 返回状态码是否是 200
}
与状态码相关的还有一系列方法,这里简单做个列举:
assertSuccessful
:断言响应状态码是否介于200-300之间;assertNotFound
:断言响应状态码是否是 404;assertForbidden
:断言响应状态码是否是 403;
此外,我们还可以通过 assertSee
或 assertSeeText
方法断言响应实体中是否包含给定字符串:
public function testSeeText()
{
$response = $this->get('/');
$response->assertSee('Laravel');
$response->assertSeeText('Laravel');
}
注:上述两个方法的区别是后者会将响应实体转化为纯文本进行判断,即将 HTML 标签过滤掉。
与之相对的,还有 assertDontSee
和 assertDontSeeText
方法,与上述判断相反,断言响应实体中不包含给定字符串。与之类似的,还有 assertSeeInOrder
以及 assertSeeTextInOrder
方法,用于断言给定字符串是否按照对应的顺序出现在响应实体中。
测试重定向
我们可以通过 assertRedirect
对重定向响应进行测试,断言重定向指向的 URL 是否与预期一致:
public function testRedirection()
{
$response = $this->get('/redirect');
$response->assertRedirect('https://laravel.geekai.co');
}
要测试这个重定向响应,我们需要确保在 routes/web.php
中包含如下路由定义:
Route::get('/redirect', function () {
return redirect('https://laravel.geekai.co');
});
然后,我们运行 phpunit
就可以让测试通过了:
此外,还有一个与之类似的方法 assertLocation
也可以用于断言重定向 URL,与 assertRedirect
不同之处在于,它不会对响应状态码和响应头进行判断,assertRedirect
会先断言响应状态码是否在 [201, 301, 302, 303, 307, 308]
数组中并且响应头中包含 Location
字段。
测试响应头
如果你想要对响应头进行深入测试,可以通过 assertHeader
方法实现:
public function testHeader()
{
$response = $this->get('/header');
$response->assertHeader('X-Header-One', 'Laravel学院')
->assertHeader('X-Header-Two', 'HTTP 功能测试');
}
为了让上述测试用例通过,我们还要在 routes/web.php
中定义如下路由:
Route::get('/header', function (){
return response('测试响应头')
->header('X-Header-One', 'Laravel学院')
->header('X-Header-Two', 'HTTP 功能测试');
});
运行 phpunit
命令,结果如下,表示测试通过:
测试 Cookie
Laravel 功能测试为 Cookie 测试提供了多个相关断言方法。要测试响应中是否包含给定 Cookie 且与指定值匹配,可以通过 assertCookie
方法实现:
public function testCookie()
{
$response = $this->get('/cookie');
$response->assertCookie('UserName', '学院君');
}
相应的,我们在 routes/web.php
中新增如下路由:
Route::get('/cookie', function (){
return response('测试 Cookie')->cookie('UserName', '学院君');
});
运行 phpunit
,测试通过:
与 Cookie 相关的测试用例还有下面这些,都比较简单,就不一一演示了:
assertCookieExpired
:断言给定 Cookie 是否过期;assertCookieNotExpired
:断言给定 Cookie 没有过期;assertCookieMissing
:断言给定 Cookie 不存在;assertPlainCookie
:断言给定 Cookie 存在且与给定值匹配(不加密)。
测试 Session
为了测试 HTTP Session,我们先在 routes/web.php
中定义一个新的路由:
Route::get('/session', function (){
session(['SiteName' => 'Laravel学院']);
session(['UserName' => '学院君']);
return response('测试 Session');
});
然后为这个路由编写测试用例:
public function testSession()
{
$response = $this->get('/session');
$response->assertSessionHas('SiteName', 'Laravel学院')
->assertSessionHas('UserName')
->assertSessionMissing('AppName');
// 一次性指定包含的 Session
$response->assertSessionHasAll(['SiteName' => 'Laravel学院', 'UserName' => '学院君']);
}
我们可以通过 assertSessionHas
方法依次断言每个 Session 存储项,也可以通过 assertSessionHasAll
方法一次性断言多个 Session 存储项,在使用它们的时候,可以指定对应的 Session 值,也可以不指定,如果指定的话则必须与存储的 Session 之匹配才会测试通过。
另外,我们可以通过 assertSessionMissing
方法断言指定 Session 存储项不存在。
表单验证错误信息
此外,我们知道,表单验证的错误信息也是存储在一次性 Session 里面的,Laravel 单独为我们提供了相关的断言方法对表单验证场景进行测试,下面我们通过一个简单的例子来演示。
首先我们在 routes/web.php
中定义一个处理表单提交的路由:
Route::post('/form', function (\Illuminate\Http\Request $request) {
$request->validate([
'title' => 'required|max:200',
'body' => 'required'
]);
return response('测试表单验证');
});
在这个路由闭包中,我们会对表单请求字段进行验证,对于不符合指定规则的字段会返回验证错误信息。然后我们在测试用例中编写相应的测试方法:
public function testForm()
{
$response = $this->post('/form', ['title' => '学院君', 'content' => '测试数据']);
$response->assertSessionHasErrors(['body' => 'The body field is required.'])
->assertSessionDoesntHaveErrors(['content' => 'The content field is required.']);
}
Laravel 会将验证错误信息存放到名为 errors
的 Session 项中,所以我们通过 assertSessionHasErrors
和 assertSessionDoesntHaveErrors
断言 Session 中是否包含/不包含对应的验证错误信息。这两个方法都支持一次性断言多个字段。
除此之外,与验证错误 Session 相关的测试方法还有用于断言验证结果不包含错误信息的 assertSessionHasNoErrors
,以及用于断言错误信息位于指定数组的 assertSessionHasErrorsIn
,该方法可以通过 assertSessionHasErrors
来替代。
下一篇我们将继续围绕 HTTP 功能测试展开,介绍如何在 Laravel 中测试用户认证、视图、文件上传和 JSON API。
No Comments