CakePHP4でRoutingのテスト

最近React+TypeScriptばかりで、CakePHPのコードはあまり書いていないわたなべです。

Routingのテスト

CakePHP1の頃の新原さんのブログ(なんと2009-05-25の記事、11年前!?)でも書かれているように、routes.phpの設定変更は、思わぬバグを出す可能性があるので、UnitTestでの動作確認は必須だと思っています。

CakePHP3までは、以下のような感じでテストできていましたが、CakePHP4でRoutingがmiddleware化した影響などでそのままでは動作しません。

<?php
declare(strict_types=1);
...
use Cake\Network\Request;
...

    public function testRouting($request, $expected)
    {
        $request = new Request([
            'url'         => '/api/articles.json',
            'environment' => ['REQUEST_METHOD' => 'GET']
        ]),
        $result = Router::parseRequest($request);
        $expected = [
            'pass'       => [],
            'plugin'     => null,
            'controller' => 'Articles',
            'prefix'     => 'api',
            '_ext'       => 'json',
            'action'        => 'index',
        ];
        $this->assertEquals($expected, $result);
    }
...

CakePHP4でのRoutingのテスト

最初書いたテストコードは以下のような感じ。

<?php
declare(strict_types=1);
...
use Cake\Http\ServerRequest;
...

    public function testRouting()
    {
        $request = new ServerRequest([
            'url'         => '/api/users/login.json',
            'environment' => ['REQUEST_METHOD' => 'POST']
        ]);
        $result = Router::parseRequest($request);
        $expected = [
            'pass' => [],
            'plugin' => null,
            'controller' => 'Users',
            'prefix' => 'Api',
            '_ext' => 'json',
            'action' => 'login',
            '_matchedRoute' => '/api/users/login',
            '_method' => ['POST'],
            '_middleware' => ['bodies']
        ];
        $this->assertEquals($expected, $result);
    }
...

これを実行すると、以下のようなエラーになってしまいます。もちろんroutes.phpには正しく設定されていて、Web経由でのアクセスにも問題ありません。

Cake\Routing\Exception\MissingRouteException: A route matching "/api/users/login.json" could not be found.

色々調べてみたところ、Routerがmiddleware化されたため、Applicationにmiddlewareが追加された時点でrouterを初期化するようになったようです。 このため、routingテーブルが空の状態になっていました。

解決方法

ということで、Routingテーブルを初期化する処理をtraitにしました。

<?php
declare(strict_types=1);

namespace App\Test\Utility;

use Cake\Routing\Router;

trait RoutingTestTrait {
    protected function initializeRoute()
    {
        Router::reload();
        $routes = Router::createRouteBuilder('/');
        require CONFIG . 'routes.php';
    }
}

前出のコードをこのtraitを使うようにこんな感じに修正するとroutingのテストができます。

<?php
declare(strict_types=1);
...
use Cake\Http\ServerRequest;
use App\Test\Utility\RoutingTestTrait;    // 追加
...
class UsersControllerTest extends TestCase
{
    use IntegrationTestTrait;
    use RoutingTestTrait;    // 追加
...

    public function testRouting()
    {
        $this->initializeRoute();    // 追加

        $request = new ServerRequest([
            'url'         => '/api/users/login.json',
            'environment' => ['REQUEST_METHOD' => 'POST']
        ]);
        $result = Router::parseRequest($request);
        $expected = [
            'pass' => [],
            'plugin' => null,
            'controller' => 'Users',
            'prefix' => 'Api',
            '_ext' => 'json',
            'action' => 'login',
            '_matchedRoute' => '/api/users/login',
            '_method' => ['POST'],
            '_middleware' => ['bodies']
        ];
        $this->assertEquals($expected, $result);
    }
...

まとめ

routingのテストを書いていたことで、routing変更時に何度も救われたことがあるので是非テストを書くことをお勧めします。 CakePHP4は、色々と改善されていい感じに進化しているので今後も使っていこうと思います。

以上、小ネタでした。

2020/6/25追記

RoutingのMiddleware化は、CakePHP3で実施されていました。具体的に何が理由でRoutingテーブルが初期化されていないかは不明。後日調べてみてわかれば追記します。