Skip to content

应用程序测试

介绍

Laravel 提供了一个非常流畅的 API,用于向您的应用程序发出 HTTP 请求、检查输出,甚至填写表单。例如,请查看下面定义的测试:

php
<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5')
             ->dontSee('Rails');
    }
}

visit 方法向应用程序发出 GET 请求。see 方法断言我们应该在应用程序返回的响应中看到给定的文本。dontSee 方法断言给定的文本未在应用程序响应中返回。这是 Laravel 中最基本的应用程序测试。

您还可以使用 visitRoute 方法通过命名路由发出 GET 请求:

php
$this->visitRoute('profile');

$this->visitRoute('profile', ['user' => 1]);

与应用程序交互

当然,您可以做的不仅仅是断言给定响应中出现的文本。让我们看看一些点击链接和填写表单的示例:

与链接交互

在此测试中,我们将向应用程序发出请求,在返回的响应中“点击”一个链接,然后断言我们到达了给定的 URI。例如,假设我们的响应中有一个文本值为“关于我们”的链接:

php
<a href="/about-us">关于我们</a>

现在,让我们编写一个测试来点击链接并断言用户到达了正确的页面:

php
public function testBasicExample()
{
    $this->visit('/')
         ->click('关于我们')
         ->seePageIs('/about-us');
}

您还可以使用 seeRouteIs 方法检查用户是否到达了正确的命名路由:

php
->seeRouteIs('profile', ['user' => 1]);

与表单交互

Laravel 还提供了几种方法来测试表单。typeselectcheckattachpress 方法允许您与表单的所有输入进行交互。例如,假设此表单存在于应用程序的注册页面上:

php
<form action="/register" method="POST">
    {{ csrf_field() }}

    <div>
        姓名: <input type="text" name="name">
    </div>

    <div>
        <input type="checkbox" value="yes" name="terms"> 接受条款
    </div>

    <div>
        <input type="submit" value="注册">
    </div>
</form>

我们可以编写一个测试来完成此表单并检查结果:

php
public function testNewUserRegistration()
{
    $this->visit('/register')
         ->type('Taylor', 'name')
         ->check('terms')
         ->press('注册')
         ->seePageIs('/dashboard');
}

当然,如果您的表单包含其他输入,例如单选按钮或下拉框,您也可以轻松填写这些类型的字段。以下是每种表单操作方法的列表:

方法描述
$this->type($text, $elementName)在给定字段中“输入”文本。
$this->select($value, $elementName)“选择”单选按钮或下拉字段。
$this->check($elementName)“选中”复选框字段。
$this->uncheck($elementName)“取消选中”复选框字段。
$this->attach($pathToFile, $elementName)将文件“附加”到表单。
$this->press($buttonTextOrElementName)按下具有给定文本或名称的按钮。

文件输入

如果您的表单包含 file 输入,您可以使用 attach 方法将文件附加到表单:

php
public function testPhotoCanBeUploaded()
{
    $this->visit('/upload')
         ->attach($pathToFile, 'photo')
         ->press('上传')
         ->see('上传成功!');
}

测试 JSON API

Laravel 还提供了几种用于测试 JSON API 及其响应的助手。例如,jsongetpostputpatchdelete 方法可用于发出具有各种 HTTP 动词的请求。您还可以轻松地将数据和头信息传递给这些方法。首先,让我们编写一个测试来向 /user 发出 POST 请求并断言返回了预期的数据:

php
<?php

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->json('POST', '/user', ['name' => 'Sally'])
             ->seeJson([
                 'created' => true,
             ]);
    }
}
lightbulb

seeJson 方法将给定数组转换为 JSON,然后验证 JSON 片段是否在应用程序返回的整个 JSON 响应中任何地方出现。因此,如果 JSON 响应中有其他属性,只要给定的片段存在,此测试仍将通过。

验证精确匹配

如果您想验证给定数组与应用程序返回的 JSON 是精确匹配的,您应该使用 seeJsonEquals 方法:

php
<?php

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->json('POST', '/user', ['name' => 'Sally'])
             ->seeJsonEquals([
                 'created' => true,
             ]);
    }
}

验证结构匹配

还可以验证 JSON 响应是否符合特定结构。在这种情况下,您应该使用 seeJsonStructure 方法并传递预期的 JSON 结构:

php
<?php

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->get('/user/1')
             ->seeJsonStructure([
                 'name',
                 'pet' => [
                     'name', 'age'
                 ]
             ]);
    }
}

上面的示例说明了期望接收到一个 name 属性和一个嵌套的 pet 对象,其中包含自己的 nameage 属性。seeJsonStructure 不会因为响应中存在其他键而失败。例如,如果 pet 具有 weight 属性,测试仍将通过。

您可以使用 * 来断言返回的 JSON 结构中有一个列表,其中每个列表项至少包含在值集中找到的属性:

php
<?php

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        // 断言列表中的每个用户至少具有 id、name 和 email 属性。
        $this->get('/users')
             ->seeJsonStructure([
                 '*' => [
                     'id', 'name', 'email'
                 ]
             ]);
    }
}

您还可以嵌套 * 符号。在这种情况下,我们将断言 JSON 响应中的每个用户包含一组给定的属性,并且每个用户的每个宠物也包含一组给定的属性:

php
$this->get('/users')
     ->seeJsonStructure([
         '*' => [
             'id', 'name', 'email', 'pets' => [
                 '*' => [
                     'name', 'age'
                 ]
             ]
         ]
     ]);

会话 / 认证

Laravel 提供了几个用于在测试期间处理会话的助手。首先,您可以使用 withSession 方法将会话数据设置为给定数组。这对于在向应用程序发出请求之前加载会话数据非常有用:

php
<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
        $this->withSession(['foo' => 'bar'])
             ->visit('/');
    }
}

当然,会话的一个常见用途是维护已认证用户的状态。actingAs 辅助方法提供了一种简单的方法来将给定用户认证为当前用户。例如,我们可以使用模型工厂生成并认证一个用户:

php
<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
        $user = factory(App\User::class)->create();

        $this->actingAs($user)
             ->withSession(['foo' => 'bar'])
             ->visit('/')
             ->see('Hello, '.$user->name);
    }
}

您还可以通过将守卫名称作为第二个参数传递给 actingAs 方法来指定应使用哪个守卫来认证给定用户:

php
$this->actingAs($user, 'api')

禁用中间件

在测试应用程序时,您可能会发现禁用某些测试的中间件很方便。这将允许您在不考虑中间件的情况下测试路由和控制器。Laravel 包含一个简单的 WithoutMiddleware trait,您可以使用它来自动禁用测试类的所有中间件:

php
<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use WithoutMiddleware;

    //
}

如果您只想为几个测试方法禁用中间件,可以在测试方法中调用 withoutMiddleware 方法:

php
<?php

class ExampleTest extends TestCase
{
    /**
     * 一个基本的功能测试示例。
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->withoutMiddleware();

        $this->visit('/')
             ->see('Laravel 5');
    }
}

自定义 HTTP 请求

如果您想向应用程序发出自定义 HTTP 请求并获取完整的 Illuminate\Http\Response 对象,可以使用 call 方法:

php
public function testApplication()
{
    $response = $this->call('GET', '/');

    $this->assertEquals(200, $response->status());
}

如果您正在发出 POSTPUTPATCH 请求,可以在请求中传递一个输入数据数组。当然,这些数据将在您的路由和控制器中通过请求实例可用:

php
$response = $this->call('POST', '/user', ['name' => 'Taylor']);

PHPUnit 断言

Laravel 为 PHPUnit 测试提供了多种自定义断言方法:

方法描述
->assertResponseOk();断言客户端响应具有 OK 状态码。
->assertResponseStatus($code);断言客户端响应具有给定代码。
->assertViewHas($key, $value = null);断言响应视图具有给定的绑定数据。
->assertViewHasAll(array $bindings);断言视图具有给定的绑定数据列表。
->assertViewMissing($key);断言响应视图缺少某个绑定数据。
->assertRedirectedTo($uri, $with = []);断言客户端是否被重定向到给定的 URI。
->assertRedirectedToRoute($name, $parameters = [], $with = []);断言客户端是否被重定向到给定的路由。
->assertRedirectedToAction($name, $parameters = [], $with = []);断言客户端是否被重定向到给定的操作。
->assertSessionHas($key, $value = null);断言会话具有给定的值。
->assertSessionHasAll(array $bindings);断言会话具有给定的值列表。
->assertSessionHasErrors($bindings = [], $format = null);断言会话绑定了错误。
->assertHasOldInput();断言会话具有旧输入。
->assertSessionMissing($key);断言会话缺少给定的键。