LoginSignup
5

More than 5 years have passed since last update.

phpspecのコラボレータとエクスペクテーションのパターン

Last updated at Posted at 2014-05-18

Loggerクラスのlog()メソッドを呼び出すと、IOクラスのwriteln()が呼ばれる、というスペックを想定する。

<?php
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class LoggerSpec extends ObjectBehavior {
    function it_can_log_message() {
        $this->log("Hello");
    }
}

引数で渡す

普通ないだろうけど単純な例として、IOオブジェクトを引数で渡すとする。

class LoggerSpec extends ObjectBehavior {
    function it_can_log_message(IO $io) {
        $io->writeln("LOG: Hello")->shouldBeCalled();
        $this->log("Hello", $io);
    }
}

スペックに仮引数を書くとphpspecが自動的にモックを作って渡してくれる。

コンストラクタで渡す

let()はxUnit系でいうところのsetUp()

class LoggerSpec extends ObjectBehavior {
    function let(IO $io) {
        $this->beConstructedWith($io);
    }

    function it_can_log_message(IO $io) {
        $io->writeln("LOG: Hello")->shouldBeCalled();
        $this->log("Hello");
    }
}

letにも同様にIO $ioを渡す。phpspecは仮引数名でモックオブジェクトをキャッシュしているので、letとスペックの仮引数名が一致していれば同じものを渡してくれる。

メソッド内でnewされたオブジェクトがコラボレータに渡された場合

IO#writeln()に文字列ではなく、Logger#log()メソッドが生成するMessageオブジェクトを渡すとしよう。この場合、単純にはMessageが一致しているかを調べることができない。

1つの解決策はFactoryクラスを導入することだろう。

class LoggerSpec extends ObjectBehavior {
    function let(IO $io, MessageFactory $factory) {
        $this->beConstructedWith($io, $factory);
    }

    function it_can_log_message(IO $io, MessageFactory $factory, Message $message) {
        $factory->createMessage("Hello")->shouldBeCalled()->willReturn($message);
        $io->writeln($message)->shouldBeCalled();
        $this->log("Hello");
    }
}

IO#writeln()に渡されるのはMessageFactory#createMessage()で作られたMessageオブジェクトである。中身は気にしない。というスペックになっている。実際このテストでは"Hello"という文字列がMessageオブジェクトに含まれているかどうかは気にしていない。モッキストらしいアプローチだ。

別の方法としては、Messageオブジェクトが何かと一致しているというエクスペクテーションを諦めて、オブジェクトの型や状態を調べる。

class LoggerSpec extends ObjectBehavior {
    function let(IO $io) {
        $this->beConstructedWith($io);
    }

    function it_can_log_message(IO $io) {
        $io->writeln(Argument::type('Message'))->shouldBeCalled();
        $this->log("Hello");
    }
}

Prophecy\\Argument::type()を使って、引数の型を調べている。さらにMessage#getMessage()が"Hello"を返すことも追加してみる。

    function it_can_log_message(IO $io) {
        $io->writeln(Argument::allOf(
            Argument::type('Message'),
            Argument::which('getMessage', 'Hello')
        ))->shouldBeCalled();
        $this->log("Hello");
    }

又は、Prophecy\\Argument::that()にコールバック関数を渡す。

    function it_can_log_message(IO $io)
    {
        $io->writeln(Argument::that(function ($v) {
            return ($v instanceof Message) && $v->getMessage() === "Hello";
        }))->shouldBeCalled();
        $this->log("Hello");
    }

基本的にphpspec自体は前者の使われ方を想定して設計されたのではないかと思う。一方ProphecyをモックフレームワークとしてPHPUnitと組み合わせて使う、といった場合には後者のような使い方も覚えておいた方が良い。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5