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と組み合わせて使う、といった場合には後者のような使い方も覚えておいた方が良い。