测试控制器

新增的辅助类与 Trait 使测试控制器更加便捷。测试控制器时,可直接运行控制器内部代码,无需经历完整的应用引导流程。通常使用 功能测试工具 会更简便,但若有特殊需求,也可使用此功能。

备注

由于未引导整个框架,某些情况下可能无法以此方式测试控制器。

辅助 Trait

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

<?php

namespace App\Controllers;

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

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

引入此 Trait 后,即可开始设置环境,包括请求和响应类、请求体、URI 等。通过 controller() 方法指定要使用的控制器,传入控制器的完全限定类名。最后,调用 execute() 方法,将方法名作为参数传入:

<?php

namespace App\Controllers;

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

class ForumControllerTest extends CIUnitTestCase
{
    use ControllerTestTrait;
    use DatabaseTestTrait;

    public function testShowCategories()
    {
        $result = $this->withUri('http://example.com/categories')
            ->controller(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)

可自定义请求体。测试 API 控制器时,如需将 JSON 数据设为请求体,此功能非常实用。唯一参数是代表请求体的字符串:

<?php

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

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

检查响应

ControllerTestTrait::execute() 返回 TestResponse 实例。有关如何在测试用例中使用此类执行额外断言和验证,请参阅 Testing Responses

测试过滤器

与控制器测试类似,框架提供的工具可用于测试自定义 过滤器 及其在路由中的应用。

辅助 Trait

与控制器测试一样,需在测试用例中引入 FilterTestTrait 以启用这些功能:

<?php

namespace App\Filters;

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

class FooFilterTest extends CIUnitTestCase
{
    use FilterTestTrait;
}

配置

由于与控制器测试逻辑重合,FilterTestTrait 可与 ControllerTestTrait 搭配使用,方便在同一个类中同时调用。 引入此 Trait 后,CIUnitTestCase 会自动检测其 setUp 方法,并准备测试所需的所有组件。如需特殊配置,可在调用支持方法前修改相关属性:

  • $request:已就绪的默认 IncomingRequest 服务实例

  • $response:已就绪的默认 ResponseInterface 服务实例

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

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

  • $collection:已就绪的 RouteCollection 实例,包含对 Config\Routes 的自动发现

默认配置最接近真实运行的项目,通常是测试的首选。但若需模拟过滤器在未配置过滤器的路由上意外触发等情况,可将其手动添加到配置中:

<?php

namespace App\Filters;

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

final class FooFilterTest 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 App\Filters;

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

final class FooFilterTest 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');