# 测试
- [简介](#introduction)
- [测试应用程序](#application-testing)
- [与你的应用程序进行交互](#interacting-with-your-application)
- [测试 JSON APIs](#testing-json-apis)
- [自定义 HTTP 请求](#custom-http-requests)
- [使用数据库](#working-with-databases)
- [每次测试结束后重置数据库](#resetting-the-database-after-each-test)
- [模型工厂](#model-factories)
- [模拟](#mocking)
- [模拟事件](#mocking-events)
- [模拟任务](#mocking-jobs)
- [模拟 Facades](#mocking-facades)
## 简介
Lumen 在创建时就已考虑到测试的部分。事实上, Lumen 默认就支持用 PHPUnit 来做测试,并为你的应用程序创建好了 `phpunit.xml` 文件。框架还提供了一些便利的辅助函数,让你可以更直观的测试应用程序。
在 `tests` 目录中有提供一个 `ExampleTest.php` 的示例文件。安装新的 Laravel 应用程序之后,只需在命令行上运行 `phpunit` 就可以进行测试。
### 测试环境
在运行测试时,Lumen 会自动将环境变量设置为 `testing`,并将 Session 及缓存以 `数组` 的形式存入,也就是说在测试时不会保存任何的 Session 或缓存数据。
你可以随意创建其它必要的测试环境配置。`testing` 的环境变量可以在 `phpunit.xml` 文件中被修改。
### 定义并运行测试
要创建一个测试案例,直接创建你的测试文件并存放在 `tests` 目录下。测试文件必须继承 `TestCase`。 接着就可以像平常使用 PHPUnit 一样来定义测试方法。要运行测试只需要在命令行上运行 `phpunit` 命令即可:
assertTrue(true);
}
}
> **注意:** 如果要在你的类自定义自己的 `setUp` 方法,请确保调用了 `parent::setUp`。
## 测试应用程序
Lumen 为 HTTP 请求的生成和发送操作、输出检查提供了非常流利的 API。
### 测试 JSON APIs
Lumen 也提供了几个辅助函数来测试 JSON API 及其响应。举例来说,`get`、`post`、`put`、`patch` 及 `delete` 方法可以用于发出各种 HTTP 动作的请求。你也可以轻松的传入数据或标头到这些方法上。首先,让我们来编写一个测试,将 `POST` 请求发送至 `/user` ,并断言其会返回 JSON 格式的指定数组:
json('POST', '/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
`seeJson` 方法会将传入的数组转换成 JSON,并验证该 JSON 片段是否存在于应用程序返回的 JSON 响应中的 **任何位置**。也就是说,即使有其它的属性存在于该 JSON 响应中,但是只要指定的片段存在,此测试便会通过。
#### 验证完全匹配的 JSON
如果你想验证传入的数组是否与应用程序返回的 JSON **完全** 匹配,则可以使用 `seeJsonEquals` 方法:
post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
### 认证
`actingAs` 辅助函数提供了简单的方式来让指定的用户认证为当前的用户:
create();
$this->actingAs($user)
->get('/user');
}
}
### 自定义 HTTP 请求
如果你想要创建一个自定义的 HTTP 请求到应用程序上,并获取完整的 `Illuminate\Http\Response` 对象,则可以使用 `call` 方法:
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
如果你创建的是 `POST`、`PUT`、或是 `PATCH` 请求,则可以在请求时传入一个数组作为输入数据。当然,你也可以在路由及控制器中通过 [请求实例](/docs/{{version}}/requests) 取用这些数据:
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
## 使用数据库
Lumen 也提供了多种有用的工具来让你更容易的测试使用数据库的应用程序。首先,你可以使用 `seeInDatabase` 辅助函数,来断言数据库中是否存在与指定条件互相匹配的数据。举例来说,如果我们想验证 `users` 数据表中是否存在 `email` 值为 `sally@example.com` 的数据,我们可以按照以下的方式来做测试:
public function testDatabase()
{
// Make call to application...
$this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}
当然,使用 `seeInDatabase` 方法及其它的辅助函数只是为了方便。你也可以随意使用 PHPUnit 内置的所有断言方法来扩充测试。
### 每次测试结束后重置数据库
在每次测试结束后都需要对数据进行重置,这样前面的测试数据就不会干扰到后面的测试。
#### 使用迁移
其中有一种方式就是在每次测试后都还原数据库,并在下次测试前运行迁移。Lumen 提供了简洁的 `DatabaseMigrations` trait,它会自动帮你处理好这些操作。你只需在测试类中使用此 trait 即可:
visit('/')
->see('Lumen.');
}
}
#### 使用事务
另一个方式,就是将每个测试案例都包含在数据库事务中。Lumen 提供了一个简洁的 `DatabaseTransactions` trait 来自动帮你处理好这些操作:
visit('/')
->see('Lumen.');
}
}
### 模型工厂
测试时,常常需要在运行测试之前写入一些数据到数据库中。创建测试数据时,除了手动的来设置每个字段的值,还可以使用 [Eloquent 模型](/docs/{{version}}/eloquent) 的「工厂」来设置每个属性的默认值。在开始之前,你可以先查看下应用程序的 `database/factories/ModelFactory.php` 文件。此文件包含一个现成的工厂定义:
$factory->define('App\User', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
];
});
闭包内为工厂的定义,你可以返回模型中所有属性的默认测试值。在该闭包内会接收到 [Faker](https://github.com/fzaninotto/Faker) PHP 函数库的实例,它可以让你很方便的生成各种随机数据以进行测试。
当然,你也可以随意将自己额外的工厂增加至 `ModelFactory.php` 文件。你也可以在。
#### 多个工厂类型
有时你可能希望针对同一个 Eloquent 模型类来创建多个工厂。例如,除了一般用户的工厂之外,还有「管理员」工厂。你可以使用 `defineAs` 方法来定义这个工厂:
$factory->defineAs('App\User', 'admin', function ($faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'admin' => true,
];
});
除了从一般用户工厂复制所有基本属性,你也可以使用 `raw` 方法来获取所有基本属性。一旦你获取到这些属性,就可以轻松的为其增加任何额外值:
$factory->defineAs('App\User', 'admin', function ($faker) use ($factory) {
$user = $factory->raw('App\User');
return array_merge($user, ['admin' => true]);
});
#### 在测试中使用工厂
在工厂定义后,就可以在测试或是数据库的填充文件中,通过全局的 `factory` 函数来生成模型实例。接着让我们先来看看几个创建模型的例子。首先我们会使用 `make` 方法创建模型,但不将它们保存至数据库:
public function testDatabase()
{
$user = factory('App\User')->make();
// 在测试中使用模型...
}
如果你想重写模型中的某些默认值,则可以传递一个包含数值的数组至 `make` 方法。只有指定的数值会被替换,其它剩余的数值则会按照工厂指定的默认值来设置:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
你也可以创建一个含有多个模型的集合,或创建一个指定类型的模型:
// 创建三个 App\User 实例...
$users = factory(App\User::class, 3)->make();
// 创建一个 App\User「管理员」实例...
$user = factory(App\User::class, 'admin')->make();
// 创建三个 App\User「管理员」实例...
$users = factory(App\User::class, 'admin', 3)->make();
#### 保存工厂模型
你不仅可使用 `create` 方法来创建模型实例,而且也可以使用 Eloquent 的 `save` 方法来将它们保存至数据库:
public function testDatabase()
{
$user = factory(App\User::class)->create();
// 在测试中使用模型...
}
同样的,你可以在数组传递至 `create` 方法时重写模型的属性:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
#### 增加关联至模型
你甚至可以保存多个模型到数据库上。在本例中,我们还会增加关联至我们所创建的模型。当使用 `create` 方法创建多个模型时,它会返回一个 Eloquent [集合实例](/docs/{{version}}/eloquent-collections),让你能够使用集合所提供的便利函数,像是 `each`:
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
## 模拟
### 模拟事件
如果你正在频繁地使用 Laravel 的事件系统,你可能希望在测试时停止或是模拟某些事件。举例来说,如果你正在测试用户注册功能,你可能不希望所有 `UserRegistered` 事件的处理进程都被运行,因为它们会触发「欢迎」邮件的发送。
Laravel 提供了简洁的 `expectsEvents` 方法,以验证预期的事件有被运行,可防止该事件的任何处理进程被运行:
expectsEvents('App\Events\UserRegistered');
// Test user registration code...
}
}
你可以使用 `doesntExpectEvents` 来验证某个事件 **没被** 触发:
withoutEvents();
// Test user registration code...
}
}
### 模拟任务
有时你可能希望当请求发送至应用程序时,简单的对控制器所派送的任务进行测试。这么做能够让你隔离测试路由或控制器,设置除了任务以外的逻辑。当然,在此之后你也可以在一个单独的测试案例中来测试该任务。
Laravel 提供了一个简洁的 `expectsJobs` 方法,以验证预期的任务有被派送,但任务本身不会被运行:
expectsJobs('App\Jobs\PurchasePodcast');
// Test purchase podcast code...
}
}
> **注意:** 此方法只检测 `DispatchesJobs` trait 的派送方法所派送出的任务。它并不会检测直接发送到 `Queue::push` 的任务。
### 模拟 Facades
测试时,你可能时常需要模拟调用一个 Laravel [facade](/docs/{{version}}/facades)。可参考下方的控制器行为:
once()
->with('key')
->andReturn('value');
$this->get('/users');
}
}
> **注意:** 你不应该模拟 `Request` facade。应该在测试时使用如 `call` 及 `post` 这样的 HTTP 辅助函数来传递你想要的数据。