介绍

Lumen在构建时就考虑了测试。实际上,开箱即用中包含对PHPUnit测试的支持,并且已经为您的应用程序设置了一个文件。该框架还附带了方便的帮助程序方法,使您可以表达性地测试应用程序的JSON响应。phpunit.xml
目录中提供了一个文件。安装新的Lumen应用程序后,只需在命令行上运行即可运行测试。ExampleTest.php``tests``phpunit

测试环境

Lumenarray在测试时会自动将缓存配置为驱动程序,这意味着在测试期间不会保留任何缓存数据。
您可以根据需要自由创建其他测试环境配置。该testing环境变量可以在中配置文件。phpunit.xml

定义和运行测试

要创建测试用例,只需在tests目录中创建一个新的测试文件。测试类应该扩展TestCase。然后,您可以像通常使用PHPUnit一样定义测试方法。要运行测试,只需phpunit从终端执行命令:

  1. <?php
  2. class FooTest extends TestCase
  3. {
  4. public function testSomethingIsTrue()
  5. {
  6. $this->assertTrue(true);
  7. }
  8. }

注意:如果您setUp在测试类中定义自己的方法,请确保调用。parent::setUp

应用测试

Lumen提供了非常流畅的API,用于向您的应用程序发出HTTP请求并检查输出。

测试JSON API

Lumen还提供了一些帮助来测试JSON API及其响应。例如,getpostputpatch,和delete方法可用于与各种HTTP动词问题的请求。您也可以轻松地将数据和标头传递给这些方法。首先,让我们编写一个测试以发出POST请求,/user并断言给定数组以JSON格式返回:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * A basic functional test example.
  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. }

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

验证确切的JSON匹配

如果要验证给定的数组与应用程序返回的JSON 完全匹配,则应使用以下seeJsonEquals方法:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. /**
  5. * A basic functional test example.
  6. *
  7. * @return void
  8. */
  9. public function testBasicExample()
  10. {
  11. $this->post('/user', ['name' => 'Sally'])
  12. ->seeJsonEquals([
  13. 'created' => true,
  14. ]);
  15. }
  16. }

认证方式

actingAs辅助方法提供了一种简单的方式来给定用户为当前用户进行身份验证:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testApplication()
  5. {
  6. $user = factory('App\User')->create();
  7. $this->actingAs($user)
  8. ->get('/user');
  9. }
  10. }

自定义HTTP请求

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

  1. public function testApplication()
  2. {
  3. $response = $this->call('GET', '/');
  4. $this->assertEquals(200, $response->status());
  5. }

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

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

使用数据库

Lumen还提供了各种有用的工具,使您可以更轻松地测试数据库驱动的应用程序。首先,您可以使用seeInDatabase助手来断言数据库中存在符合给定条件的数据。例如,如果我们要验证users表中是否存在一条记录,其email值为,则可以执行以下操作:sally@example.com

  1. public function testDatabase()
  2. {
  3. // Make call to application...
  4. $this->seeInDatabase('users', ['email' => 'sally@foo.com']);
  5. }

当然,该seeInDatabase方法和其他类似的辅助方法是为了方便起见。您可以自由使用PHPUnit的任何内置断言方法来补充测试。

每次测试后重置数据库

每次测试后重置数据库通常很有用,这样前一次测试中的数据就不会干扰后续测试。

使用迁移

一种选择是在每次测试之后回滚数据库,并在下一次测试之前迁移数据库。Lumen提供了一个简单的DatabaseMigrations特征,它将为您自动处理。只需在测试类中使用特征:

  1. <?php
  2. use Laravel\Lumen\Testing\DatabaseMigrations;
  3. use Laravel\Lumen\Testing\DatabaseTransactions;
  4. class ExampleTest extends TestCase
  5. {
  6. use DatabaseMigrations;
  7. /**
  8. * A basic functional test example.
  9. *
  10. * @return void
  11. */
  12. public function testBasicExample()
  13. {
  14. $this->get('/foo');
  15. }
  16. }

使用交易

另一种选择是将每个测试用例包装在数据库事务中。同样,Lumen提供了一个方便的DatabaseTransactions特征,它将自动处理此问题:

  1. <?php
  2. use Laravel\Lumen\Testing\DatabaseMigrations;
  3. use Laravel\Lumen\Testing\DatabaseTransactions;
  4. class ExampleTest extends TestCase
  5. {
  6. use DatabaseTransactions;
  7. /**
  8. * A basic functional test example.
  9. *
  10. * @return void
  11. */
  12. public function testBasicExample()
  13. {
  14. $this->get('/foo');
  15. }
  16. }

模型工厂

测试时,通常需要在执行测试之前向数据库中插入一些记录。Lumen允许您使用“工厂” 为每个Eloquent模型定义默认属性集,而无需在创建此测试数据时手动指定每列的值。首先,请查看应用程序中的文件。开箱即用,此文件包含一个出厂定义:database/factories/ModelFactory.php

  1. $factory->define('App\User', function ($faker) {
  2. return [
  3. 'name' => $faker->name,
  4. 'email' => $faker->email,
  5. ];
  6. });

在作为工厂定义的Closure中,您可以返回模型上所有属性的默认测试值。该Closure将接收Faker PHP库的实例,该实例使您可以方便地生成各种随机数据以进行测试。
当然,您可以自由地将自己的其他工厂添加到文件中。ModelFactory.php

多种工厂类型

有时您可能希望为同一个Eloquent模型类拥有多个工厂。例如,也许您希望除了普通用户外还为“管理员”用户提供工厂。您可以使用以下defineAs方法定义这些工厂:

  1. $factory->defineAs('App\User', 'admin', function ($faker) {
  2. return [
  3. 'name' => $faker->name,
  4. 'email' => $faker->email,
  5. 'admin' => true,
  6. ];
  7. });

您可以使用该raw方法来检索基本属性,而不是从基本用户工厂复制所有属性。获得属性后,只需用所需的任何其他值补充它们:

  1. $factory->defineAs('App\User', 'admin', function ($faker) use ($factory) {
  2. $user = $factory->raw('App\User');
  3. return array_merge($user, ['admin' => true]);
  4. });

在测试中使用工厂

定义工厂后,可以在测试或数据库种子文件中使用它们,以使用全局factory函数生成模型实例。因此,让我们看一些创建模型的示例。首先,我们将使用该make方法,该方法创建模型但不将其保存到数据库中:

  1. public function testDatabase()
  2. {
  3. $user = factory('App\User')->make();
  4. // Use model in tests...
  5. }

如果您想覆盖模型的某些默认值,则可以将值数组传递给该make方法。只有指定的值将被替换,而其余的值则保持出厂设置的默认值:

  1. $user = factory('App\User')->make([
  2. 'name' => 'Abigail',
  3. ]);

您还可以创建许多模型的集合或创建给定类型的模型:

  1. // Create three App\User instances...
  2. $users = factory('App\User', 3)->make();
  3. // Create an App\User "admin" instance...
  4. $user = factory('App\User', 'admin')->make();
  5. // Create three App\User "admin" instances...
  6. $users = factory('App\User', 'admin', 3)->make();

坚持工厂模式

create方法不仅创建模型实例,而且使用Eloquent的save方法将它们保存到数据库中:

  1. public function testDatabase()
  2. {
  3. $user = factory('App\User')->create();
  4. // Use model in tests...
  5. }

同样,您可以通过将数组传递给create方法来覆盖模型上的属性:

  1. $user = factory('App\User')->create([
  2. 'name' => 'Abigail',
  3. ]);

向模型添加关系

您甚至可以将多个模型持久化到数据库中。在这个例子中,我们甚至将一个关系附加到创建的模型上。使用该create方法创建多个模型时,将返回一个Eloquent 集合实例,使您可以使用集合提供的任何便捷功能,例如each

  1. $users = factory('App\User', 3)
  2. ->create()
  3. ->each(function($u) {
  4. $u->posts()->save(factory('App\Post')->make());
  5. });

嘲笑

模拟事件

如果您大量使用Lumen的事件系统,则可能希望在测试时静默或嘲笑某些事件。例如,如果您正在测试用户注册,则可能不希望UserRegistered触发事件的所有处理程序,因为这些处理程序可能会发送“欢迎”电子邮件等。
Lumen提供了一种方便的expectsEvents方法来验证是否触发了预期的事件,但是阻止了这些事件的任何处理程序运行:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testUserRegistration()
  5. {
  6. $this->expectsEvents('App\Events\UserRegistered');
  7. // Test user registration code...
  8. }
  9. }

如果要阻止所有事件处理程序运行,则可以使用以下withoutEvents方法:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testUserRegistration()
  5. {
  6. $this->withoutEvents();
  7. // Test user registration code...
  8. }
  9. }

模拟工作

有时,您可能希望在向应用程序发出请求时仅测试控制器是否调度了特定作业。这使您可以隔离测试路由/控制器-与工作逻辑分开设置。当然,您可以在单独的测试类中测试作业本身。
Lumen提供了一种方便的expectsJobs方法,该方法将验证是否已分发预期的作业,但作业本身将不会执行:

  1. <?php
  2. class ExampleTest extends TestCase
  3. {
  4. public function testPurchasePodcast()
  5. {
  6. $this->expectsJobs('App\Jobs\PurchasePodcast');
  7. // Test purchase podcast code...
  8. }
  9. }

注意:此方法仅检测通过dispatch全局帮助器功能或通过路由或控制器的方法分派的作业。它不会检测直接发送到的作业。$this->dispatch``Queue::push

模拟门面

测试时,您可能经常想模拟对Lumen 门面的调用。例如,考虑以下控制器操作:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Cache;
  4. class UserController extends Controller
  5. {
  6. /**
  7. * Show a list of all users of the application.
  8. *
  9. * @return Response
  10. */
  11. public function index()
  12. {
  13. $value = Cache::get('key');
  14. //
  15. }
  16. }

我们可以Cache使用shouldReceive方法模拟对外观的调用,该方法将返回Mockery模拟的实例。由于外墙实际上是由Lumen 服务容器解析和管理的,因此它们比典型的静态类具有更高的可测试性。例如,让我们模拟对Cache外观的调用:

  1. <?php
  2. class FooTest extends TestCase
  3. {
  4. public function testGetIndex()
  5. {
  6. Cache::shouldReceive('get')
  7. ->once()
  8. ->with('key')
  9. ->andReturn('value');
  10. $this->get('/users');
  11. }
  12. }

注意:您不应该模拟Request外观。相反,通过你的愿望成HTTP helper方法输入如callpost运行测试时。