测试控制器

使用几个新的助手类和特征,可以方便地测试您的控制器。在测试控制器时,您可以在不先运行整个应用程序引导过程的情况下执行控制器中的代码。通常情况下,使用 功能测试工具 会更简单,但如果需要,此功能就在这里。

注意

由于整个框架尚未引导,因此有时您无法以这种方式测试控制器。

助手特征

要启用控制器测试,您需要在测试中使用 ControllerTestTrait 特征

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\ControllerTestTrait;
use CodeIgniter\Test\DatabaseTestTrait;

class TestControllerA extends CIUnitTestCase
{
    use ControllerTestTrait;
    use DatabaseTestTrait;
}

包含特征后,您可以开始设置环境,包括请求和响应类、请求主体、URI 等等。您可以使用 controller() 方法指定要使用的控制器,并将控制器的完全限定类名作为参数传递。最后,使用要运行的方法的名称作为参数调用 execute() 方法

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\ControllerTestTrait;
use CodeIgniter\Test\DatabaseTestTrait;

class TestControllerA extends CIUnitTestCase
{
    use ControllerTestTrait;
    use DatabaseTestTrait;

    public function testShowCategories()
    {
        $result = $this->withURI('http://example.com/categories')
            ->controller(\App\Controllers\ForumController::class)
            ->execute('showCategories');

        $this->assertTrue($result->isOK());
    }
}

辅助方法

controller($class)

指定要测试的控制器的类名。第一个参数必须是完全限定的类名(即包括命名空间)

<?php

$this->controller(\App\Controllers\ForumController::class);

execute(string $method, …$params)

在控制器中执行指定的方法。第一个参数是要运行的方法的名称

<?php

$results = $this->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

通过指定第二个和后续参数,您可以将它们传递给控制器方法。

这将返回一个新的辅助类,该类提供了一些用于检查响应本身的例程。有关详细信息,请参见下文。

withConfig($config)

允许您传入 **app/Config/App.php** 的修改版本,以使用不同的设置进行测试

<?php

$config              = new \Config\App();
$config->appTimezone = 'America/Chicago';

$results = $this->withConfig($config)
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

如果您没有提供一个,将使用应用程序的 App 配置文件。

withRequest($request)

允许您提供一个适合您的测试需求的 **IncomingRequest** 实例

<?php

$request = new \CodeIgniter\HTTP\IncomingRequest(
    new \Config\App(),
    new \CodeIgniter\HTTP\URI('http://example.com'),
    null,
    new \CodeIgniter\HTTP\UserAgent()
);

$request->setLocale($locale);

$results = $this->withRequest($request)
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

如果您没有提供一个,将使用具有默认应用程序值的新的 IncomingRequest 实例传递到您的控制器。

withResponse($response)

允许您提供一个 **Response** 实例

<?php

$response = new \CodeIgniter\HTTP\Response(new \Config\App());

$results = $this->withResponse($response)
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

如果您没有提供一个,将使用具有默认应用程序值的新的 Response 实例传递到您的控制器。

withLogger($logger)

允许您提供一个 **Logger** 实例

<?php

$logger = new \CodeIgniter\Log\Handlers\FileHandler();

$results = $this->withResponse($response)
    ->withLogger($logger)
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

如果您没有提供一个,将使用具有默认配置值的新的 Logger 实例传递到您的控制器。

withURI(string $uri)

允许您提供一个新的 URI,模拟运行此控制器时客户端访问的 URL。如果您需要检查控制器中的 URI 段,这将很有用。唯一的参数是一个表示有效 URI 的字符串

<?php

$results = $this->withURI('http://example.com/forums/categories')
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

在测试过程中始终提供 URI 是一个好习惯,以避免出现意外情况。

注意

从 v4.4.0 版本开始,此方法使用 URI 创建一个新的 Request 实例。因为 Request 实例应该包含 URI 实例。此外,如果 URI 字符串中的主机名与 Config\App 中的有效主机名不匹配,则会设置有效主机名。

withBody($body)

允许您为请求提供自定义主体。当测试需要将 JSON 值设置为主体的 API 控制器时,这很有用。唯一的参数是一个表示请求主体的字符串。

<?php

$body = json_encode(['foo' => 'bar']);

$results = $this->withBody($body)
    ->controller(\App\Controllers\ForumController::class)
    ->execute('showCategories');

检查响应

ControllerTestTrait::execute() 返回一个 TestResponse 实例。有关如何使用此类在测试用例中执行其他断言和验证,请参阅 测试响应

过滤器测试

与控制器测试类似,框架提供工具来帮助创建自定义 过滤器 的测试,以及您的项目在路由中使用它们。

助手特质

就像控制器测试一样,您需要在测试用例中包含 FilterTestTrait 来启用这些功能。

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FilterTestTrait;

class FilterTestCase extends CIUnitTestCase
{
    use FilterTestTrait;
}

配置

由于与控制器测试的逻辑重叠,FilterTestTrait 被设计为与 ControllerTestTrait 一起工作,如果您需要在同一个类中使用两者。一旦包含了特质,CIUnitTestCase 将检测其 setUp 方法并准备测试所需的所有组件。如果您需要特殊配置,您可以在调用支持方法之前更改任何属性。

  • $request 默认 IncomingRequest 服务的准备版本

  • $response 默认 ResponseInterface 服务的准备版本

  • $filtersConfig 默认 Config\Filters 配置(注意:发现由 Filters 处理,因此不包括模块别名)

  • $filters 使用上述三个组件的 CodeIgniter\Filters\Filters 实例

  • $collection RouteCollection 的准备版本,其中包括 Config\Routes 的发现

默认配置通常最适合您的测试,因为它最接近“实时”项目,但(例如)如果您想模拟过滤器在未过滤路由上意外触发,您可以将其添加到配置中

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FilterTestTrait;

final class FilterTestCase extends CIUnitTestCase
{
    use FilterTestTrait;

    protected function testFilterFailsOnAdminRoute()
    {
        $this->filtersConfig->globals['before'] = ['admin-only-filter'];

        $this->assertHasFilters('unfiltered/route', 'before');
    }

    // ...
}

检查路由

第一个辅助方法是 getFiltersForRoute(),它将模拟提供的路由并返回一个过滤器列表(按其别名),这些过滤器将在给定位置(“before”或“after”)运行,而不会实际执行任何控制器或路由代码。与控制器和 HTTP 测试相比,这具有很大的性能优势。

getFiltersForRoute($route, $position)
参数:
  • $route (string) – 要检查的 URI

  • $position (string) – 要检查的过滤器方法,“before”或“after”

返回值:

将运行的每个过滤器的别名

返回类型:

string[]

使用示例

<?php

$result = $this->getFiltersForRoute('/', 'after'); // ['toolbar']

调用过滤器方法

配置中描述的属性都已设置,以确保最大性能,而不会干扰或受到其他测试的干扰。下一个辅助方法将使用这些属性返回一个可调用方法,以安全地测试您的过滤器代码并检查结果。

getFilterCaller($filter, $position)
参数:
  • $filter (FilterInterface|string) – 过滤器实例、类或别名

  • $position (string) – 要运行的过滤器方法,“before” 或 “after”

返回值:

一个可调用方法,用于运行模拟的过滤器事件

返回类型:

Closure

使用示例

<?php

namespace CodeIgniter;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FilterTestTrait;

final class FilterTestCase extends CIUnitTestCase
{
    use FilterTestTrait;

    protected function testUnauthorizedAccessRedirects()
    {
        $caller = $this->getFilterCaller('permission', 'before');
        $result = $caller('MayEditWidgets');

        $this->assertInstanceOf('CodeIgniter\HTTP\RedirectResponse', $result);
    }
}

注意 Closure 可以接受传递给您的过滤器方法的输入参数。

断言

除了上面的辅助方法外,FilterTestTrait 还提供了一些断言来简化您的测试方法。

assertFilter()

assertFilter() 方法检查给定位置的路由是否使用过滤器(通过其别名)。

<?php

// Make sure users are logged in before checking their account
$this->assertFilter('users/account', 'before', 'login');

assertNotFilter()

assertNotFilter() 方法检查给定位置的路由是否不使用过滤器(通过其别名)。

<?php

// Make sure API calls do not try to use the Debug Toolbar
$this->assertNotFilter('api/v1/widgets', 'after', 'toolbar');

assertHasFilters()

assertHasFilters() 方法检查给定位置的路由是否至少设置了一个过滤器。

<?php

// Make sure that filters are enabled
$this->assertHasFilters('filtered/route', 'after');

assertNotHasFilters()

assertNotHasFilters() 方法检查给定位置的路由是否没有设置任何过滤器。

<?php

// Make sure no filters run for our static pages
$this->assertNotHasFilters('about/contact', 'before');