CodeIgniter4のフィーチャーテスト

この記事は CodeIgniter Advent Calendar 2020 - Qiita の14日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

フィーチャーテストとは?

CodeIgniter4 にはフィーチャーテストの機能が含まれています。

フィーチャーテストは Web アプリへのリクエストサイクル全体をテストする結合テストで、ci-phpunit-test での $this->request() に相当するものです。

Web アプリにリクエストを擬似的に送信し、その結果についてテストするということで、直感的にわかりやすいものです。

テストのための設定

プロジェクトルートに PHPUnit の設定ファイル phpunit.xml.dist があります。これを、phpunit.xml としてコピーし、自分の環境に合うように変更します。

app.baseURL.env で設定していますので削除します。

--- phpunit.xml.dist    2020-12-14 09:16:19.000000000 +0900
+++ phpunit.xml 2020-12-14 09:16:19.000000000 +0900
@@ -36,8 +36,6 @@
    </logging>

    <php>
-       <server name="app.baseURL" value="http://example.com"/>
-
        <!-- Directory containing phpunit.xml -->
        <const name="HOMEPATH" value="./"/>

phpunit.xml はプロジェクトで共有するので、git add -f で追加してバージョン管理しましょう。

テストファイルの作成

Welcome ページをリクエストするテストを作成してみましょう。

tests/feature フォルダを作成し、tests/feature/HomeTest.php を作成します。

tests/feature/HomeTest.php

<?php

namespace Tests\Feature;

use CodeIgniter\Test\FeatureTestCase;

class HomeTest extends FeatureTestCase
{
    public function testGetIndex()
    {
        $result = $this->call('get', '/');
        dd($result);
    }
}

GET メソッドでパス / に、リクエストを送信するコードです。

dd() 関数で、取得した結果を表示してみます。

返り値の確認

テストを実行してみましょう。

$ vendor/bin/phpunit tests/feature/HomeTest.php
PHPUnit 8.5.13 by Sebastian Bergmann and contributors.

Error:         XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set


┌──────────────────────────────────────────────────────────────────────────────┐
│ $result                                                                      │
└──────────────────────────────────────────────────────────────────────────────┘
CodeIgniter\Test\FeatureResponse (45) (
    public 'response' -> CodeIgniter\HTTP\Response (17) (
        protected 'reason' -> string (2) "OK"
        protected 'statusCode' -> integer 200
        protected 'CSPEnabled' -> boolean false
        public 'CSP' -> CodeIgniter\HTTP\ContentSecurityPolicy (22) (
            protected 'baseURI' -> null
            protected 'childSrc' -> string (4) "self"
            protected 'connectSrc' -> string (4) "self"
            protected 'defaultSrc' -> null
            protected 'fontSrc' -> null
            protected 'formAction' -> string (4) "self"
            protected 'frameAncestors' -> null
            protected 'imageSrc' -> string (4) "self"
            protected 'mediaSrc' -> null
            protected 'objectSrc' -> string (4) "self"
            protected 'pluginTypes' -> null
            protected 'reportURI' -> null
            protected 'sandbox' -> null
            protected 'scriptSrc' -> string (4) "self"
            protected 'styleSrc' -> string (4) "self"
            protected 'manifestSrc' -> null
            protected 'upgradeInsecureRequests' -> boolean false
            protected 'reportOnly' -> boolean false
            protected 'validSources' -> array (4) [
                0 => string (4) "self"
                1 => string (4) "none"
                2 => string (13) "unsafe-inline"
                3 => string (11) "unsafe-eval"
            ]
            protected 'nonces' -> array (0) []
            protected 'tempHeaders' -> array (0) []
            protected 'reportOnlyHeaders' -> array (0) []
        )
        protected 'cookiePrefix' -> string (0) ""
        protected 'cookieDomain' -> string (0) ""
        protected 'cookiePath' -> string (1) "/"
        protected 'cookieSecure' -> boolean false
        protected 'cookieHTTPOnly' -> boolean false
        protected 'cookies' -> array (0) []
        protected 'pretend' -> boolean false
        protected 'bodyFormat' -> string (4) "html"
        protected 'headers' -> array (2) [
            'Cache-control' => CodeIgniter\HTTP\Header (2) (
                protected 'name' -> string (13) "Cache-control"
                protected 'value' -> array (3) [
                    0 => string (8) "no-store"
                    1 => string (9) "max-age=0"
                    2 => string (8) "no-cache"
                ]
            )
            'Content-Type' => CodeIgniter\HTTP\Header (2) (
                protected 'name' -> string (12) "Content-Type"
                protected 'value' -> string (24) "text/html; charset=UTF-8"
            )
        ]
        protected 'headerMap' -> array (2) [
            'cache-control' => string (13) "Cache-control"
            'content-type' => string (12) "Content-Type"
        ]
        protected 'protocolVersion' -> string (3) "1.1"
        protected 'validProtocolVersions' -> array (3) [
            0 => string (3) "1.0"
            1 => string (3) "1.1"
            2 => string (1) "2"
        ]
        protected 'body' -> string (18385) "<!-- DEBUG-VIEW START 1 APPPATH/Config/../Views/welcome_message.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Welcome to CodeIgniter 4!</title>
    <meta name="description" content="The small framework with powerful features">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" type="image/png" href="/favicon.ico"/>

    <!-- STYLES -->

    <style {csp-style-nonce}>
        ...
    </style>
</head>
<body>

<!-- HEADER: MENU + HEROE SECTION -->
<header>

    <div class="menu">
        <ul>
            <li class="logo"><a href="https://codeigniter.com" target="_blank"><img height="44" title="CodeIgniter Logo"
                                        alt="Visit CodeIgniter.com official website!"
                                        src="data:image/png;base64,..."></a>
            </li>
            <li class="menu-toggle">
                <button onclick="toggleMenu();">&#9776;</button>
            </li>
            <li class="menu-item hidden"><a href="#">Home</a></li>
            <li class="menu-item hidden"><a href="https://codeigniter4.github.io/userguide/" target="_blank">Docs</a>
            </li>
            <li class="menu-item hidden"><a href="https://forum.codeigniter.com/" target="_blank">Community</a></li>
            <li class="menu-item hidden"><a
                    href="https://github.com/codeigniter4/CodeIgniter4/blob/master/CONTRIBUTING.md" target="_blank">Contribute</a>
            </li>
        </ul>
    </div>

    <div class="heroe">

        <h1>Welcome to CodeIgniter 4.0.4</h1>

        <h2>The small framework with powerful features</h2>

    </div>

</header>

<!-- CONTENT -->

<section>

    <h1>About this page</h1>

    <p>The page you are looking at is being generated dynamically by CodeIgniter.</p>

    <p>If you would like to edit this page you will find it located at:</p>

    <pre><code>app/Views/welcome_message.php</code></pre>

    <p>The corresponding controller for this page can be found at:</p>

    <pre><code>app/Controllers/Home.php</code></pre>

</section>

<div class="further">

    <section>

        <h1>Go further</h1>

        <h2>
            <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>...</svg>
            Learn
        </h2>

        <p>The User Guide contains an introduction, tutorial, a number of "how to"
            guides, and then reference documentation for the components that make up
            the framework. Check the <a href="https://codeigniter4.github.io/userguide"
            target="_blank">User Guide</a> !</p>

        <h2>
            <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>...</svg>
            Discuss
        </h2>

        <p>CodeIgniter is a community-developed open source project, with several
             venues for the community members to gather and exchange ideas. View all
             the threads on <a href="https://forum.codeigniter.com/"
             target="_blank">CodeIgniter's forum</a>, or <a href="https://codeigniterchat.slack.com/"
             target="_blank">chat on Slack</a> !</p>

        <h2>
             <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>...</svg>
             Contribute
        </h2>

        <p>CodeIgniter is a community driven project and accepts contributions
             of code and documentation from the community. Why not
             <a href="https://codeigniter.com/en/contribute" target="_blank">
             join us</a> ?</p>

    </section>

</div>

<!-- FOOTER: DEBUG INFO + COPYRIGHTS -->

<footer>
    <div class="environment">

        <p>Page rendered in 0.0181 seconds</p>

        <p>Environment: testing</p>

    </div>

    <div class="copyrights">

        <p>&copy; 2020 CodeIgniter Foundation. CodeIgniter is open source project released under the MIT
            open source licence.</p>

    </div>

</footer>

...

</body>
</html>

<!-- DEBUG-VIEW ENDED 1 APPPATH/Config/../Views/welcome_message.php -->
"
    )
    protected 'domParser' -> CodeIgniter\Test\DOMParser (1) (
        protected 'dom' -> DOMDocument (0) ()
    )
    protected 'backupGlobals' -> null
    protected 'backupGlobalsBlacklist' -> array (0) []
    protected 'backupStaticAttributes' -> null
    protected 'backupStaticAttributesBlacklist' -> array (0) []
    protected 'runTestInSeparateProcess' -> null
    protected 'preserveGlobalState' -> boolean true
    private 'runClassInSeparateProcess' -> null
    private 'inIsolation' -> boolean false
    private 'data' -> null
    private 'dataName' -> null
    private 'expectedException' -> null
    private 'expectedExceptionMessage' -> null
    private 'expectedExceptionMessageRegExp' -> null
    private 'expectedExceptionCode' -> null
    private 'name' -> string (0) ""
    private 'dependencies' -> array (0) []
    private 'dependencyInput' -> array (0) []
    private 'iniSettings' -> array (0) []
    private 'locale' -> array (0) []
    private 'mockObjects' -> array (0) []
    private 'mockObjectGenerator' -> null
    private 'status' -> integer -1
    private 'statusMessage' -> string (0) ""
    private 'numAssertions' -> integer 0
    private 'result' -> null
    private 'testResult' -> null
    private 'output' -> string (0) ""
    private 'outputExpectedRegex' -> null
    private 'outputExpectedString' -> null
    private 'outputCallback' -> boolean false
    private 'outputBufferingActive' -> boolean false
    private 'outputBufferingLevel' -> null
    private 'outputRetrievedForAssertion' -> boolean false
    private 'snapshot' -> null
    private 'prophet' -> null
    private 'beStrictAboutChangesToGlobalState' -> boolean false
    private 'registerMockObjectsFromTestArgumentsRecursively' -> boolean false
    private 'warnings' -> array (0) []
    private 'groups' -> array (0) []
    private 'doesNotPerformAssertions' -> boolean false
    private 'customComparators' -> array (0) []
    private 'doubledTypes' -> array (0) []
    private 'deprecatedExpectExceptionMessageRegExpUsed' -> boolean false
)
════════════════════════════════════════════════════════════════════════════════
Called from <ROOT>/tests/feature/HomeTest.php:12 [dd()]

$this->call()CodeIgniter\Test\FeatureResponse オブジェクトを返す。その response プロパティに CodeIgniter\HTTP\Response オブジェクトが含まれていることがわかります。

テストコードの作成

流れがわかりましたので、テストコードを作成していきましょう。

HTTP ステータスやレスポンスに含まれる文字列を検証します。

tests/feature/HomeTest.php

<?php

namespace Tests\Feature;

use CodeIgniter\Test\FeatureTestCase;

class HomeTest extends FeatureTestCase
{
    public function testGetIndex()
    {
        $result = $this->call('get', '/');
//        dd($result);

        $result->assertStatus(200);

        $result->assertSee('<title>Welcome to CodeIgniter 4!</title>');
        $result->assertSee('Environment: testing');
    }
}

ステータスコードが 200 であることと、<title> タグの文字列、それと環境が testing になっていることを確認します。

テストの実行

phpunit を実行します。

$ vendor/bin/phpunit tests/feature/HomeTest.php
PHPUnit 8.5.13 by Sebastian Bergmann and contributors.

Error:         XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set

.                                                                   1 / 1 (100%)

Time: 302 ms, Memory: 10.00 MB

OK (1 test, 3 assertions)

通りました。

テスト実行中は CodeIgniter4 の環境が testing になっていることも確認できました。

CodeIgniter4のフィーチャーテストでデータベースを使う に続きます。

この記事は CodeIgniter Advent Calendar 2020 - Qiita の14日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。

参考

Date: 2020/12/14

Tags: codeigniter, codeigniter4, testing