🍰

PhakeのモックでCakePHPをテスト コンポーネント篇

2020/09/25に公開

こんにちは。Phake のモックで CakePHP をテスト コントローラ篇・改では PHPUnit 標準のモック機構を(ほぼ)使わずPhakeフレームワークのみで CakePHP の Controller をテストする手法について述べました。

そのとき、Component も交えてのテストが可能だったため「コンポーネント篇」を書く予定は無くなったのですが、今回 単独の Component に対してテストする 必要に迫られたため改めて書くことにしました。

単独の Component のモック生成には一工夫必要

Controller と違い依存が少ないためModel のテストと同じように簡単だろうと思っていたらハマりました。

class MyComponent extends Component
{
    public function __construct(ComponentCollection $collection, array $settings = array())
    {
        parent::__construct($collection, $settings);
        $this->controller = $collection->getController();
        return $this;
    }

    // 略
}

このような ComponentMyComponentに対してテストを書きたいとします。

class MyComponentTest extends CakeTestCase
{
    /**
     * @return void
     */
    public function setUp()
    {
        parent::setUp();

        $this->any = Phake::anyParameters();

        $this->mockCtrl = Phake::mock('AppController');
        $collection     = Phake::partialMock('ComponentCollection', $this->mockCtrl);

        Phake::when($collection)    ->getController()     ->thenReturn($this->mockCtrl);
        Phake::when($this->mockCtrl)->redirect($this->any)->thenReturn(null);

        $this->MyComponent = new MyComponent($collection);

        // 以下はテスト内容に応じて他のComponentを利用する際などに
        $this->mockSession = Phake::mock('SessionComponent');
        Phake::when($this->mockSession)->setFlash($this->any)->thenReturn(null);
        $this->MyComponent->Session = $this->mockSession;
    }

    // 略
}

結論として、setUp()はこうなります。

ComponentCollection のモック化がくせもの

Phake のモック生成にはPhake::mock()というメソッドがあり通常はこれで問題ないのですが、まれにモックが生成できない場合があります。CakePHP といったライブラリが内部で特定のオブジェクトを要求する場合などです。

今回、ComponentCollectionController型のインスタンスを内部で要求しているためPhake::mock('ComponentCollection')ではController型のインスタンスが不足してしまいます。このため例外が発せられます。

Phake::partialMock()を使う

Phake にはPhake::partialMock()というメソッドも用意されています。パーシャルモックはスパイとも呼ばれ、クラスの振る舞いを変えずに(Phake::When()->thenReturn()などをせずに)回数や呼び出し順を検証するために使います。

このメソッドは引数を 2 つ受け付けます。Phake::partialMock($className, $value)の形式で、$classNameにはクラス名文字列、$valueにはコンストラクタに与える値やインスタンスを任意で渡します。

今回はComponentCollectionが要求するController型のインスタンスを第 2 引数に渡すことで、モックが生成できるのです。テスト内では抽象 Controller で十分なため、AppControllerのモックを渡しました。

$this->mockCtrl = Phake::mock('AppController');
$collection     = Phake::partialMock('ComponentCollection', $this->mockCtrl);

そのため上記のようになります。

Component で他の Component を使う際

MyComponentから CakePHP 標準のSessionComponentを使いたい! そんな時は簡単。

$this->mockSession = Phake::mock('SessionComponent');
Phake::when($this->mockSession)->setFlash($this->any)->thenReturn(null);
$this->MyComponent->Session = $this->mockSession;

何も考えずにPhake::mock($className)で生成できます。


Phake::mock()Phake::partialMock()を使いこなすことで、あらゆる Class に対してテストが書けそうですね。PHP ではPhakeを使って快適モックライフを過ごしましょう!

Discussion