CakePHPのテストコード追加

CakePHPのテストコードを作成します。

参考:公式ページ

古いですが、CakePHP3、PHP7.3で試します。

PHPUnitとCakePHP独自のテスト機能があるみたいです。

インストール・環境構築

公式ドキュメントをご確認ください。
PHPUnitのインストールされてない方はインストールするくらいだと思います。

テストケース作成

以下のルールに従ってテストコードを記述するファイルを作成します。
試しにControllerのテストコードを作成します。

  • テストを含むPHPファイルは、 tests/TestCase/[Type] ディレクトリーに格納
    • 今回、Controllerのテストコードを記述するので、tests/TestCase/Controller配下に格納します。
  • ファイル名の最後は必ずTest.php とする。
  • 以下のいずれかのクラスを継承する必要がある。
    • Cake\TestSuite\TestCase
    • Cake\TestSuite\IntegrationTestCase
    • \PHPUnit\Framework\TestCase
  • テストケースのクラス名はファイル名と一致する必要がある。
  • テストを含むメソッド (つまり、アサーションを含むメソッド) は以下のいずれかのルールに従う必要がある。
    • メソッド名を testPublished() のように test で始める。
    •  @test というアノテーションをメソッドに マークする。
<?php
namespace App\Test\TestCase\Controller\;

use App\Controller\<テスト対象クラス名>;
use Cake\ORM\Entity;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestCase;

/**
 * App\Controller\<テスト対象クラス名> Test Case
 */
class <テスト対象クラス名>Test extends IntegrationTestCase
{
    /**
     * Fixtures
     *
     * @var array
     */
    public $fixtures = [
        // 必要に応じてフィクスチャをロードしてください
    ];

    private $table1;
    private $ApiComponent;
    private $AclComponent;
    private $_eventListener;

    /**
     * setUp method
     * 
     * テストケースクラスのメソッドが呼び出される前に毎回実行されます。
     * 各オブジェクトの初期化やモックの設定を行います。
     */
    public function setUp(): void
    {
        parent::setUp();

        $mockTableData = [
            new Entity(['key' => 'name', 'value' => '1']),
            new Entity(['key' => 'kana', 'value' => '1']),
            new Entity(['key' => 'tel', 'value' => '1']),
        ];

        // --- テーブルのモック ---
        $this->table1 = $this->getMockForModel('table1', ['deleteAll', 'saveMany', 'newEntities', '<その他独自のfunctionなど>']);

        //  functionがモックデータを返すように設定します
        // これにより、コントローラがデータを取得しようとした際にエラーが発生するのを防ぎます
        $this->table1->method('<返却値を設定したいfunction')->willReturn($mockTableData);

        // コンポーネントのモックを作成します
        $this->ApiComponent = $this->getMockBuilder('App\Controller\Component\ApiComponent')
            ->setMethods(['<mockで定義したいfunction>'])
            ->disableOriginalConstructor()
            ->getMock();

        // アプリケーション固有のAclComponentをモックします。ビューで使われているメソッドも対象に含めます。
        $this->AclComponent = $this->getMockBuilder('App\Controller\Component\AclComponent')
            ->disableOriginalConstructor()
            ->setMethods(['set', '<mockで定義したいfunction>'])
            ->getMock();

        // AclComponentのメソッドが常にtrueを返すように設定します
        // これにより、setScreenInfo()内の権限チェックでnullが返ってエラーになるのを防ぎます
        $this->AclComponent->method('<mockで定義したいfunctionA>')->willReturn(true);
        $this->AclComponent->method('<mockで定義したいfunctionB>')->willReturn('test');

        // セッションのモック設定
        $this->Session = $this->getMockBuilder('Cake\Controller\Component\SessionComponent')
            ->setMethods(['read', 'write', 'delete'])
            ->disableOriginalConstructor()
            ->getMock();
        $this->Session->method('read')->willReturn(null);
        $this->Session->method('write')->willReturn(null);
        $this->Session->method('delete')->willReturn(null);

        // Controller.startupイベントをフックするためのリスナーを定義します
        $this->_eventListener = function (Event $event) {
            $controller = $event->subject();
            // コンポーネントを差し替えます
            if ($controller instanceof <テスト対象クラス名>) {
                $controller->Api = $this->ApiComponent;
                $controller->Google = $this->GoogleComponent;
                $controller->Acl = $this->AclComponent;
            }
        };
        \Cake\Event\EventManager::instance()->on('Controller.startup', $this->_eventListener);

        // 認証ユーザーとセッション、CSRF/Securityトークンを有効にします
  〜〜〜〜
    }

    /**
     * tearDown method
     * 
     * テストケースクラスのメソッドが呼び出された後に毎回実行されます。
     * 各オブジェクトのクリーンアップを行います。
     */
    public function tearDown(): void
    {
        // グローバルなイベントリスナーを解除します
        if ($this->_eventListener) {
            \Cake\Event\EventManager::instance()->off('Controller.startup', $this->_eventListener);
            $this->_eventListener = null;
        }
        TableRegistry::clear();
        parent::tearDown();
    }

    /**
     * indexアクション(GET)とビュー(index.ctp)のテスト
     */
    public function testIndexGetAndRenderView(): void
    {
        // GETリクエストを送信
        $this->get('<URLパス>/index');

        // HTTPステータスコードが200系であることを確認
        $this->assertResponseOk();
        // 表示されているテンプレートが正しいことを確認
        $this->assertTemplate('index');

        // --- index.ctp の表示内容を検証 ---
        // フォームの存在確認
        $this->assertResponseContains('<form id="form1"');
        // JavaScriptに変数が渡されていることを確認
        $this->assertResponseRegExp('/var numeric = 1;/');
    }
}

ログ

最初なぜか標準出力のログだけを追ってデバッグしてて、難易度高いなと思ってたんですが、
普通にlogs配下にログが出てました。

実行

以下のコマンドを実行

vendor/bin/phpunit

特定のファイルだけ実行したい場合

vendor/bin/phpunit <ファイル名> (tests/TestCase/〜)

コメントする