本文转载[ Laravel 5.1 文档 ] 服务 —— 测试
简介
Laravel
植根于测试,实际上,内置使用 PHPUnit
对测试提供支持是即开即用的,并且 phpunit.xml
文件已经为应用设置好了。框架还提供了方便的帮助方法允许你对应用进行富有表现力的测试。
tests
目录中提供了一个 ExampleTest.php
文件,安装完新的 Laravel
应用后,只需简单在命令行运行 phpunit
来运行测试。
测试环境
运行测试的时候,Laravel
自动设置配置环境为 testing
。Laravel
在测试时自动配置 session
和 cache
驱动为数组驱动,这意味着测试时不会持久化存储 session
和 cache
。
如果需要的话你可以自由创建其它测试环境配置。testing
环境变量可以在 phpunit.xml
文件中配置。
定义&运行测试
要创建一个测试用例,只需简单在 tests
目录创建一个新的测试文件,测试类应该继承 TestCase
,然后你可以使用 PHPUnit
定义测试方法。要运行测试,简单从终端执行 phpunit
命令即可:
1 | class FooTest extends TestCase { |
说明: 如果你在测试类中定义自己的
setUp
方法,确保在其中调用parent::setUp
。
应用测试
Laravel
为生成 HTTP
请求、测试输出、以及填充表单提供了平滑的 API
。举个例子,我们看下 tests
目录下包含的 ExampleTest.php
文件:
1 | use Illuminate\Foundation\Testing\WithoutMiddleware; |
visit
方法生成了一个 GET
请求,see
方法对我们从应用返回响应中应该看到的给定文本进行断言。dontSee
方法对给定文本没有从应用响应中返回进行断言。在 Laravel
中这是最基本的有效应用测试。
与应用交互
点击链接
在本测试中,我们将为应用生成请求,在返回的响应中“点击”链接,然后对访问URI进行断言。例如,假定响应中有一个“关于我们”的链接:
1 | <a href="/about-us">About Us</a> |
现在,让我们编写一个测试点击链接并断言用户访问页面是否正确:
1 | public function testBasicExample(){ |
处理表单
Laravel
还为处理表单提供了多个方法。type
, select
, check
, attach
, 和 press
方法允许你与所有表单输入进行交互。例如,我们假设这个表单存在于应用注册页面:
1 | <form action="/register" method="POST"> |
我们可以编写测试完成表单并检查结果:
1 | public function testNewUserRegistration(){ |
当然,如果你的表单包含其他输入比如单选按钮或下拉列表,也可以轻松填写这些字段类型。这里是所有表单操作方法列表:
方法 | 描述 |
---|---|
$this->type($text, $elementName) |
“Type ” 文本到给定字段 |
$this->select($value, $elementName) |
“Select ” 单选框或下拉列表 |
$this->check($elementName) |
“Check ” 复选框 |
$this->attach($pathToFile, $elementName) |
“Attach ” 文件到表单 |
$this->press($buttonTextOrElementName) |
“Press ” 给定文本或name的按钮 |
处理附件
如果表单包含 file
输入类型,可以使用 attach
方法添加文件到表单:
1 | public function testPhotoCanBeUploaded(){ |
测试 JSON Api
Laravel
还提供多个帮助函数用于测试 JSON API
及其响应。例如,get
, post
, put
, patch
, 和 delete
方法用于通过多种 HTTP
请求方式发出请求。你还可以轻松传递数据和头到这些方法。作为开始,我们编写测试来生成 POST
请求到 /user
并断言返回的数据是否是 JSON
格式:
1 | class ExampleTest extends TestCase{ |
seeJson
方法将给定数组转化为 JSON
,然后验证应用返回的整个 JSON
响应中的 JSON
片段。因此,如果在 JSON
响应中有其他属性,只要给定片段存在的话测试依然会通过。
如果你想要验证给定数组和应用返回的 JSON
能够精确匹配,使用 seeJsonEquals
方法:
1 | public function testBasicExample() { |
Session 认证
Laravel
提供个多个帮助函数在测试期间处理 session
,首先,可以使用 withSession
方法设置 session
值到给定数组。这通常在测试请求前获取 session
数据时很有用:
1 | class ExampleTest extends TestCase{ |
当然,session
的通常用于操作用户状态,例如认证用户。帮助函数 actingAs
提供了认证给定用户为当前用户的简单方法,例如,我们使用模型工厂生成和认证用户:
1 |
|
禁止中间件
测试应用时,为某些测试禁止中间件很方便。这种机制允许你将路由和控制器与中间件孤立开来做测试,Laravel
包含了一个简单的 WithoutMiddleware trait
,可以使用该 trait
自动在测试类中禁止所有中间件:
1 | use Illuminate\Foundation\Testing\WithoutMiddleware; |
如果你只想在某些方法中禁止中间件,可以在测试方法中调用 withoutMiddleware
方法:
1 | class ExampleTest extends TestCase{ |
自定义 Http 请求
如果你想要在应用中生成自定义 HTTP
请求并获取完整的 Illuminate\Http\Response
对象,可以使用 call
方法:
1 | public function testApplication(){ |
如果你要生成 POST
, PUT
, 或者 PATCH
请求可以在请求中传入输入数据数组,在路由或控制器中可以通过 Request
实例访问请求数据:
1 | $response = $this->call('POST', '/user', ['name' => 'Taylor']); |
处理数据库
Laravel
还提供了多种有用的工具让测试数据库驱动的应用更加简单。首先,你可以使用帮助函数 seeInDatabase
来断言数据库中的数据是否和给定数据集合匹配。例如,如果你想要通过 email
值为 `sally@example.com的条件去数据表
users` 查询是否存在该记录 ,我们可以这样做:
1 | public function testDatabase(){ |
每次测试后重置数据库
每次测试后重置数据库通常很有用,这样的话上次测试的数据不会影响下一次测试。
使用迁移
一种方式是每次测试后回滚数据库并在下次测试前重新迁移。Laravel
提供了一个简单的 DatabaseMigrations trait
来自动为你处理。在测试类上简单使用该 trait
如下:
1 |
|
使用事务
另一种方式是将每一个测试用例包裹到一个数据库事务中,Laravel
提供了方便的 DatabaseTransactions trait自动为你处理:
1 |
|
模型工厂
测试时,通常需要在执行测试前插入新数据到数据库。在创建测试数据时,Laravel
允许你使用”factories
”为每个 Eloquent
模型定义默认的属性值集合,而不用手动为每一列指定值。作为开始,我们看一下 database/factories/ModelFactory.php
文件,该文件包含了一个工厂定义:
1 | $factory->define(App\User::class, function (Faker\Generator $faker) { |
在闭包中,作为工厂定义,我们返回该模型上所有属性默认测试值。该闭包接收PHP
库 Faker
实例,从而允许你方便地为测试生成多种类型的随机数据。
当然,你可以添加更多工厂到 ModelFactory.php
文件。
多个工厂类型
有时候你可能想要为同一个 Eloquent
模型类生成多个工厂,例如,除了正常用户外可能你想要为“管理员”用户生成一个工厂,你可以使用 defineAs
方法定义这些工厂:
1 | $factory->defineAs(App\User::class, 'admin', function ($faker) { |
你可以使用 raw
方法获取基本属性而不用重复基本用户工厂中的所有属性,获取这些属性后,只需将你要求的额外值增补进去即可:
1 | $factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) { |
在测试中使用工厂
定义好工厂后,可以在测试或数据库填充文件中通过全局的 factory
方法使用它们来生成模型实例,所以,让我们看一些生成模型的例子,首先,我们使用 make
方法,该方法创建模型但不将其保存到数据库:
1 | public function testDatabase(){ |
如果你想要覆盖模型的一些默认值,可以传递数组值到 make
方法。只有指定值被替换,其他数据保持不变:
1 | $user = factory(App\User::class)->make([ |
还可以创建多个模型集合或者创建给定类型的集合:
1 | // 创建3个 App\User 实例... |
持久化工厂模型
create
方法不仅能创建模型实例,还可以使用 Eloquent
的 save
方法将它们保存到数据库:
1 | public function testDatabase(){ |
你仍然可以通过传递数组到 create
方法覆盖模型上的属性:
1 | $user = factory(App\User::class)->create([ |
添加关联关系到模型
你甚至可以持久化多个模型到数据库。在本例中,我们添加一个关联到创建的模型,使用create方法创建多个模型的时候,会返回一个 Eloquent
集合实例,从而允许你使用集合提供的所有便利方法,例如 each
:
1 | $users = factory(App\User::class, 3) |
模拟
模拟事件
如果你在重度使用 Laravel
的时间系统,可能想要在测试时模拟特定事件。例如,如果你在测试用户注册,你可能不想所有 UserRegistered
的时间处理器都被触发,因为这可能会发送欢迎邮件,等等。
Laravel
提供可一个方便的 expectsEvents
方法来验证期望的事件被触发,但同时阻止该事件的其它处理器运行:
1 | class ExampleTest extends TestCase { |
如果你想要阻止所有事件运行,可以使用 withoutEvents
方法:
1 | class ExampleTest extends TestCase { |
模拟队列任务
有时候,你可能想要在请求时简单测试控制器分发的指定任务,这允许你孤立的测试路由/控制器——将其从任务逻辑中分离出去,当然,接下来你可以在一个独立测试类中测试任务本身。
Laravel
提供了一个方便的 expectsJobs
方法来验证期望的任务被分发,但该任务本身不会被测试:
1 | class ExampleTest extends TestCase { |
注意:这个方法只检查通过
DispatchesJobs trait
分发方法分发的任务,并不检查直接通过Queue::push
分发的任务。
模拟门面
测试的时候,你可能经常想要模拟 Laravel
门面的调用,例如,看看下面的控制器动作:
1 | class UserController extends Controller{ |
我们可以通过使用 shouldReceive
方法模拟 Cache
门面的调用,该方法返回一个 Mockery
模拟的实例,由于门面通过 Laravel
服务容器解析和管理,它们比通常的静态类更具有可测试性。例如,我们来模拟 Cache
门面的调用:
1 | <?php |
注意:不要模拟
Request
门面,取而代之地,在测试时传递输入到HTTP
帮助函数如call
和post
。