测试应用程序

简介

Laravel 为 HTTP 请求的生成和发送操作、输出的检查,甚至表单的填写都提供了非常流利的 API。举例来说,你可以看看下面的这个测试用例:

  1. <?php
  2. use Illuminate\Foundation\Testing\WithoutMiddleware;
  3. use Illuminate\Foundation\Testing\DatabaseTransactions;
  4. class ExampleTest extends TestCase
  5. {
  6. /**
  7. * 基本的测试用例。
  8. *
  9. * @return void
  10. */
  11. public function testBasicExample()
  12. {
  13. $this->visit('/')
  14. ->see('Laravel 5')
  15. ->dontSee('Rails');
  16. }
  17. }

visit 方法会创建一个 GET 请求,see 方法则断言在返回的响应中会有指定的文本,dontSee 方法断言在返回的响应中不会有指定的文本。这是 Laravel 所提供的最基本的应用程序测试。

你也可以使用 visitRoute 方法来创建一个 GET 请求访问命名路由。

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

与你的应用程序进行交互

当然,除了断言文本是否存在于一个指定的响应中,你还可以做更多的交互。让我们来看看点击链接及填写表单的例子:

与链接交互

在这个测试中,我们会生成一个请求并「点击」返回响应中的链接,接着断言我们会停留在指定的 URI 上。举个例子,假设在响应中有个链接,并写着「About Us」:

  1. <a href="/about-us">About Us</a>

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

  1. public function testBasicExample()
  2. {
  3. $this->visit('/')
  4. ->click('About Us')
  5. ->seePageIs('/about-us');
  6. }

你也可以使用 seeRouteIs 方法来检查用户是否跳转到了正确的路由:

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

与表单交互

Laravel 还提供了几种用于测试表单的方法。通过 typeselectcheckattachpress 方法让你与表单的所有输入框进行交互。举个例子,设想有一个在应用程序注册页面的表单:

  1. <form action="/register" method="POST">
  2. {{ csrf_field() }}
  3. <div>
  4. Name: <input type="text" name="name">
  5. </div>
  6. <div>
  7. <input type="checkbox" value="yes" name="terms"> Accept Terms
  8. </div>
  9. <div>
  10. <input type="submit" value="Register">
  11. </div>
  12. </form>

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

  1. public function testNewUserRegistration()
  2. {
  3. $this->visit('/register')
  4. ->type('Taylor', 'name')
  5. ->check('terms')
  6. ->press('Register')
  7. ->seePageIs('/dashboard');
  8. }

当然,如果你的表单中包含了类似单选框或下拉式菜单的其它输入框,也可以很轻松的填入这些类型的区域。以下是每个表单的方法操作列表:

方法 说明
$this->type($text, $elementName) 「输入(type)」文本在一个指定的区域
$this->select($value, $elementName) 「选择(select)」一个单选框或下拉式菜单的区域
$this->check($elementName) 「勾选(check)」一个复选框的区域
$this->uncheck($elementName) 「取消勾选(uncheck)」一个复选框的区域
$this->attach($pathToFile, $elementName) 「附加(attach)」一个文件至表单
$this->press($buttonTextOrElementName) 「按下(press)」一个指定文本或名称的按钮 text or name.

文件上传

如果你的表单包含 上传文件 的输入框类型,则可以使用 attach 方法来附加文件:

  1. public function testPhotoCanBeUploaded()
  2. {
  3. $this->visit('/upload')
  4. ->attach($pathToFile, 'photo')
  5. ->press('Upload')
  6. ->see('Upload Successful!');
  7. }

测试 JSON APIs

Laravel 也提供了几个辅助函数来测试 JSON API 及其响应。举例来说,getpostputpatchdelete 方法可以用于发出各种 HTTP 动作的请求。你也可以轻松的传入数据或标头到这些方法上。首先,让我们来编写一个测试,将 POST 请求发送至 /user ,并断言其会返回 JSON 格式的指定数组:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * 基本的功能测试示例。
  6. *
  7. * @return void
  8. */
  9. public function testBasicExample()
  10. {
  11. $this->json('POST', '/user', ['name' => 'Sally'])
  12. ->seeJson([
  13. 'created' => true,
  14. ]);
  15. }
  16. }

{tip} seeJson 方法会将传入的数组转换成 JSON,并验证该 JSON 片段是否存在于应用程序返回的 JSON 响应中的 任何位置。也就是说,即使有其它的属性存在于该 JSON 响应中,但是只要指定的片段存在,此测试便会通过。

验证完全匹配的 JSON

如果你想验证传入的数组是否与应用程序返回的 JSON 完全 匹配,则可以使用 seeJsonEquals 方法:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * 基本的功能测试示例。
  6. *
  7. * @return void
  8. */
  9. public function testBasicExample()
  10. {
  11. $this->json('POST', '/user', ['name' => 'Sally'])
  12. ->seeJsonEquals([
  13. 'created' => true,
  14. ]);
  15. }
  16. }

验证 JSON 的结构匹配

你可以使用 seeJsonStructure 来验证 JSON 结构是否符合你的预期:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * 基本的功能测试示例。
  6. *
  7. * @return void
  8. */
  9. public function testBasicExample()
  10. {
  11. $this->get('/user/1')
  12. ->seeJsonStructure([
  13. 'name',
  14. 'pet' => [
  15. 'name', 'age'
  16. ]
  17. ]);
  18. }
  19. }

上面这个例子展示接受到的数据会有一个 name 属性和一个拥有 nameage 属性的嵌套 pet 对象。seeJsonStructure 并不会因为响应结果中包含额外的属性而失败。比如,即使响应中 pet 对象含有一个 weight 属性,测试依旧会通过。

你可以使用 * 来假设所有的列表内容都包含至少数组里面的内容:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * 基本的功能测试示例。
  6. *
  7. * @return void
  8. */
  9. public function testBasicExample()
  10. {
  11. // 每一个列表里至少拥有 id, name 和 email 属性
  12. $this->get('/users')
  13. ->seeJsonStructure([
  14. '*' => [
  15. 'id', 'name', 'email'
  16. ]
  17. ]);
  18. }
  19. }

你也可以灵活的使用 * 做嵌套。在下面这个实例中,我们可以断言在响应数据中每个 user 都包含指定的属性,而且用户的每个 pet 对象也包含指定的属性集:

  1. $this->get('/users')
  2. ->seeJsonStructure([
  3. '*' => [
  4. 'id', 'name', 'email', 'pets' => [
  5. '*' => [
  6. 'name', 'age'
  7. ]
  8. ]
  9. ]
  10. ]);

Sessions 和认证

Laravel 提供了几个可在测试时使用 Session 的辅助函数。首先,你需要设置 Session 数据至指定的数组中,并使用 withSession 方法。在应用程序的测试请求发送之前,可先给数据加载 session:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testApplication()
  5. {
  6. $this->withSession(['foo' => 'bar'])
  7. ->visit('/');
  8. }
  9. }

当然,一般使用 Session 时都是用于维持用户的状态,如认证用户。actingAs 辅助函数提供了简单的方式来让指定的用户认证为当前的用户。举个例子,我们可以使用 模型工厂 来生成并认证用户:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testApplication()
  5. {
  6. $user = factory(App\User::class)->create();
  7. $this->actingAs($user)
  8. ->withSession(['foo' => 'bar'])
  9. ->visit('/')
  10. ->see('Hello, '.$user->name);
  11. }
  12. }

你也可以使用 actingAs 来指定用户使用哪个 guard:

  1. $this->actingAs($user, 'api')

停用中间件

测试应用程序时,你会发现,在某些测试中停用 中间件 是很方便的。这让你可以隔离任何中间件的所有影响,以便更好的测试路由及控制器。Laravel 包含了一个简洁的 WithoutMiddleware trait,你能在测试类中使用它来自动停用所有的中间件:

  1. <?php
  2. use Illuminate\Foundation\Testing\WithoutMiddleware;
  3. use Illuminate\Foundation\Testing\DatabaseMigrations;
  4. use Illuminate\Foundation\Testing\DatabaseTransactions;
  5. class ExampleTest extends TestCase
  6. {
  7. use WithoutMiddleware;
  8. //
  9. }

如果你只想要在某几个测试方法中停用中间件,则可以在测试方法中调用 withoutMiddleware 方法:

<?php

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

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

自定义 HTTP 请求

如果你想要创建一个自定义的 HTTP 请求到应用程序上,并获取完整的 Illuminate\Http\Response 对象,则可以使用 call 方法:

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

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

如果你创建的是 POSTPUT、或是 PATCH 请求,则可以在请求时传入一个数组作为输入数据。当然,你也可以在路由及控制器中通过 请求实例 取用这些数据:

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

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); 断言 session 中有指定的值。
->assertSessionHasAll(array $bindings); 断言 session 中有指定的列表值。
->assertSessionHasErrors($bindings = [], $format = null); 断言 session 有错误的绑定。
->assertHasOldInput(); 断言 session 有旧的输入数据。
->assertSessionMissing($key); 断言 session 中没有指定的值。

译者署名

用户名 头像 职能 签名
@JobsLong 应用程序测试 - 图1 翻译 我的个人主页:http://jobslong.com

{note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。

转载请注明:本文档由 Laravel China 社区 [laravel-china.org] 组织翻译,详见 翻译召集帖

文档永久地址: http://d.laravel-china.org