ci-phpunit-testでcodeigniter-restserverをテストする方法

もうタイトルの時点でマニアックでニーズあるかわかりませんが、ci-phpunit-testを使って、codeigniter-restserverで書いたコードをテストする方法です。備忘録。

概要

何となく、codeigniter-restserverをPHPUnitで叩いたら動くかなぁと思っていたのですが、実際にcodeigniter-restserverを叩いてみると

$this->request();

の処理終了後に勝手にexit()処理が走ってしまうのです。当然、テストは止まってしまうし、そのあとのテストは走りません。
いろいろ調べてみると、悪さをしているのはcodeigniter-restserverの

$this->response();

でした。なので、こいつをやめて

$this->output
    ->set_content_type('application/json')
    ->set_status_header('204')
    ->set_output(json_encode($user));

とか書いてやると、テストは通る。だからこうやって書いていこうかなぁと思ったら、ci-phpunit-testの作者の@kenji_sさんが「ドキュメントしてありますが、そのままではテストできません」と。
ちゃんとサードパーティ対応までしてるなんてすごいなぁと思いながら、英語ドキュメントなので、日本語でやり方を簡単にまとめておきます。なお、ドキュメントはこちらです。

対応させる手順

Monkey Patchingを有効化させる(ci-phpunit-test)

テスト対象にci-phpunit-testが走ってる時に拾うcatchを追加することができるようになります。「tests/Bootstrap.php」の以下のコードを有効化させます(デフォルトはコメントアウト)

require __DIR__ . '/_ci_phpunit_test/patcher/bootstrap.php';
MonkeyPatchManager::init([
    // PHP Parser: PREFER_PHP7, PREFER_PHP5, ONLY_PHP7, ONLY_PHP5
    'php_parser' => 'PREFER_PHP5',
    'cache_dir' => APPPATH . 'tests/_ci_phpunit_test/tmp/cache',
    // Directories to patch on source files
    'include_paths' => [
        APPPATH,
        BASEPATH,
        APPPATH . 'tests/_ci_phpunit_test/replacing/',
    ],
    // Excluding directories to patch
    'exclude_paths' => [
        APPPATH . 'tests/',
        '-' . APPPATH . 'tests/_ci_phpunit_test/replacing/',
    ],
    // All patchers you use.
    'patcher_list' => [
        'ExitPatcher',
        'FunctionPatcher',
        'MethodPatcher',
    ],
    // Additional functions to patch
    'functions_to_patch' => [
        //'random_string',
    ],
    'exit_exception_classname' => 'CIPHPUnitTestExitException',
]);

$this->responseの前に処理を追加する(codeigniter-restserver)

application/libraries/REST_Controller.php に + の部分を追加します。652行目あたりです。

        try
        {
            call_user_func_array([$this, $controller_method], $arguments);
        }
+       catch (CIPHPUnitTestExitException $ex)
+       {
+           // This block is for ci-phpunit-test
+           throw $ex;
+       }
        catch (Exception $ex)
        {
            // If the method doesn't exist, then the error will be caught and an error response shown
            $this->response([
                    $this->config->item('rest_status_field_name') => FALSE,
                    $this->config->item('rest_message_field_name') => [
                        'classname' => get_class($ex),
                        'message' => $ex->getMessage()
                    ]
                ], self::HTTP_INTERNAL_SERVER_ERROR);
        }

requireをrequire_onceに変更(controller)

サンプルControllerでは、codeigniter-restserverをrequireで呼び出していますが、require_onceに変更します。3行目ぐらい。

require_once APPPATH . '/libraries/REST_Controller.php';

テストの書き方

以下のような形で、try catchでテスト書いていってください。

<?php

class Example_test extends TestCase
{
    public function test_users_get()
    {
        try {
            $output = $this->request('GET', 'api/example/users');
        } catch (CIPHPUnitTestExitException $e) {
            $output = ob_get_clean();
        }

        $this->assertEquals(
            '[{"id":1,"name":"John","email":"john@example.com","fact":"Loves coding"},{"id":2,"name":"Jim","email":"jim@example.com","fact":"Developed on CodeIgniter"},{"id":3,"name":"Jane","email":"jane@example.com","fact":"Lives in the USA","0":{"hobbies":["guitar","cycling"]}}]',
            $output
        );
        $this->assertResponseCode(200);
    }
}

以上、お役に立ちましたらー。それでは、また。

お問い合わせ