以下はhttps://github.com/padraic/mockeryの抄訳。
Mockery
MockeryはシンプルだがフレキシブルなユニットテストのためのPHP用のモックオブジェクトフレームワークである。 これはRubyのflexmockやJavaもMockitoにインスパイアされており、それらのAPIの要素を借用している。
Mockeryは新しいBSDライセンスの元で配布される。
Mockオブジェクト
ユニットテストでは、モックオブジェクトはリアルオブジェクトのふるまいをシミュレートする。 それらは、未作成のオブジェクトの代役を努めたり、実装を行うことなくAPIの実験的なデザインを行うためのものである。
モックオブジェクトフレームワークのメリットとしては、モックオブジェクト(あるいはスタブ)のフレキシブルな生成を行えることにある。 フレキシブルなAPIを使用して、期待されるメソッド呼び出しと返り値を設定することができる。 これらのAPIはすべての可能なリアルオブジェクトのふるまいをキャプチャすることができるが、それはできる限り自然言語の記述に近くなるようにしてある。
必要条件
MockeryにはPHP 5.3がが必要である。必要条件はこれがすべてである。
インストール
PEARによるインストールが推奨される。 Mockeryはthe Survivethedeepend.comのPEARチャネルによってホストされている。
pear channel-discover pear.survivethedeepend.com pear install deepend/Mockery
gitリポジトリには、開発バージョンがホストされている。 開発バージョンをインストールしたい場合は次のようにする。
git clone git://github.com/padraic/mockery.git cd mockery sudo pear install package.xml
上記手順はMockeryをPEARライブラリとしてインストールする。
簡単なサンプル
平均気温を報告するまえに、ある場所での温度をサンプルするTemperatureというクラスがあるとしよう。 データはウェブサービスを通じて得られるかもしれないし、あるいは他のデータソースかもしれないが、現時点ではそれらが存在しないものとする。 しかし、それらのクラスとの基本的なインタラクションは仮定することができる。
class Temperature { public function __construct($service) { $this->_service = $service; } public function average() { $total = 0; for ($i=0;$i<3;$i++) { $total += $this->_service->readTemp(); } return $total/3; } }
実際のサービスクラスが無くとも、期待する動作を定義することはできる。 Temperatureクラスのテストを記述する際には、実際のサービスの代わりをモックオブジェクトにさせることができる。
注意: PHPUnitインテグレーション(後述)を使えばteardown()メソッドが不要になる。
use \Mockery as m; class TemperatureTest extends extends PHPUnit_Framework_TestCase { public function teardown() { m::close(); } public function testGetsAverageTemperatureFromThreeServiceReadings() { $service = m::mock('service'); $service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14); $temperature = new Temperature($service); $this->assertEquals(12, $temperature->average()); } }
これらのAPIについては後述する。
PHPUnitインテグレーション
Mockeryはスタンドアロンのモックオブジェクトフレームワークとして使用できるようシンプルなデザインとなっているので、 テスティングフレームワークとのインタラクションはすべてオプションである。 それらと統合するには、単にtearDown()メソッドを定義するだけである。
public function teardown() { \Mockery::close(); }
この静的メソッドは、現在のテストで使用中のMockeryコンテナをクリーンアップし、 君のエクスペクテーションに必要なすべての検証タスクを実行する。
Mockeryのネームスペースを短いエイリアスとして使うことができる。例えば、
use \Mockery as m; class SimpleTest extends extends PHPUnit_Framework_TestCase { public function testSimpleMock() { $mock = m::mock('simple mock'); $mock->shouldReceive('foo')->with(5, m::any())->once()->andReturn(10); $this->assertEquals(10, $mock->foo(5)); } public function teardown() { m::close(); } }
Mockeryにはオートローダが付属しているから、テストコードにrequire_once()を書き散らかす必要はない。 Mockeryを使いには、それがinclude_pathに含まれていることを確認し、その上で君のテストスイートのブートストラップあるいはTestHelperファイルに以下のように記述すればよい。
require_once 'Mockery/Loader.php'; $loader = new \Mockery\Loader; $loader->register();
クイックリファレンス
Mockeryはモック生成のショートハンドAPIを提供している。 以下は可能なスタートアップメソッドの例である。
$mock = \Mockery::mock('foo');
これはfooという名前のモックオブジェクトを作成する。この場合には、fooという名前(必ずしもクラス名でなくてよい)は例外発生時の単純な識別子として使用される。これは、\Mockery\Mock型のモックオブジェクトを作成して、モックとしては可能な限りlooseなフォームである。
$mock = \Mockery::mock(array('foo'=>1,'bar'=>2));
ここでは名称を指定していないので名前無しのモックオブジェクトを作成するが、 その代わりに期待値配列を渡している。これは、その返り値として期待する値をメソッドに設定する素早い方法である。
$mock = \Mockery::mock('foo', array('foo'=>1,'bar'=>2));
上と同様だが、名前と期待値配列を設定する例である。
$mock = \Mockery::mock('stdClass');
名前付きのモックの生成と同一だが、ただしこの場合の名前は実際のクラス名である。 以前の例と同様の単純なモックを生成するが、ただしこのオブジェクトは指定されたクラス型を継承する。つまり、 stdClassのtype hintsやinstanceof評価を持つ。 これは指定型のモックが必要な場合に有用である。
$mock = \Mockery::mock('FooInterface');
コンクリートクラス、抽象クラス、インターフェースのいずれをベースとしてもモックオブジェクトを作成できる。 第一の目的は、type hintingの利用である。このために、指定された型を継承するモックを作成する。
$mock = \Mockery::mock('FooInterface', array('foo'=>1,'bar'=>2));
名前付きモックと同様の簡単なセットアップをクラス志向のモック生成にも用いることができる。
$mock = \Mockery::mock(new Foo);
実際のオブジェクトをMockeryに渡すと、「部分モック」を生成する。 既存のコンクリートなオブジェクトの一部のメソッドだけをオーバライド(あるいは、存在しないメソッドも)することができる。
$mock = \Mockery::mock(new Foo, array('foo'=>1));
部分モックの素早いセットアップをすることができる。 この先のセクションに部分モックについての詳細な説明がある。
$mock = \Mockery::mock('name', function($mock){ $mock->shouldReceive(method_name); });
これらの様々なセットアップメソッドには最後の引数としてクロージャを渡すことができる。 クロージャにはモックオブジェクトが渡されるので、expectationsをセットアップすることができる。
- Distinct from the later explained default expectations, this allows for the reuse of expectation setups by storing them to a closure for execution. Note that all other parameters including quick expectation arrays set prior to the closure will be used before the closure is called.
エクスペクテーションの宣言
モックオブジェクトを生成したら、その振る舞い(あるいは、それがどのように呼び出されるか)を定義したいだろう。 Mockeryのエクスペクテーション宣言がそれを引き受ける。
shouldReceive(method_name)
モックの指定された名前のメソッドが呼び出されることを宣言する。 これは他に追加されるすべてのエクスペクテーションや制約の開始ポイントである。
shouldReceive(method1, method2, ...)
複数のメソッドコールが行われることを宣言する。すべてのチェインされたエクスペクテーションや制約がこれらに適用される。
shouldReceive(array(method1=>1, method2=>2, ...))
複数のメソッドコールとその返り値を宣言する。すべてのチェインされたエクスペクテーションや制約がこれらに適用される。
shouldReceive(closure)
(部分モックからのみだが)モックオブジェクトを生成し、それはモックオブジェクトレコーダーの生成のために使用される。 レコーダーはモック生成時に引き渡されたオリジナルのオブジェクトの単純なプロキシである。
This is passed to the closure, which may run it through a set of operations which are recorded as expectations on the partial mock. A simple use case is automatically recording expectations based on an existing usage (e.g. during refactoring). See examples in a later section.
with(arg1, arg2, ...)
Adds a constraint that this expectation only applies to method calls which match the expected argument list. You can add a lot more flexibility to argument matching using the built in matcher classes (see later). For example, \Mockery::any() matches any argument passed to that position in the with() parameter list.
It's important to note that this means all expectations attached only apply to the given method when it is called with these exact arguments. Allows for setting up differing expectations based on the arguments provided to expected calls.
withAnyArgs()
Declares that this expectation matches a method call regardless of what arguments are passed. This is set by default unless otherwise specified.
withNoArgs()
Declares this expectation matches method calls with zero arguments.
andReturn(value)
Sets a value to be returned from the expected method call.
andReturn(value1, value2, ...)
Sets up a sequence of return values or closures. For example, the first call will return value1 and the second value2. Not that all subsequent calls to a mocked method will always return the final value (or the only value) given to this declaration.
andReturnUsing(closure, ...)
Sets a closure (anonymous function) to be called with the arguments passed to the method. The return value from the closure is then returned. Useful for some dynamic processing of arguments into related concrete results. Closures can queued by passing them as extra parameters as for andReturn(). Note that you cannot currently mix andReturnUsing() with andReturn().
andThrow(Exception)
Declares that this method will throw the given Exception object when called.
andThrow(exception_name, message)
Rather than an object, you can pass in the Exception class and message to use when throwing an Exception from the mocked method.
zeroOrMoreTimes()
Declares that the expected method may be called zero or more times. This is the default for all methods unless otherwise set.
once()
Declares that the expected method may only be called once. Like all other call count constraints, it will throw a \Mockery\CountValidator\Exception if breached and can be modified by the atLeast() and atMost() constraints.
twice()
Declares that the expected method may only be called twice.
times(n)
Declares that the expected method may only be called n times.
never()
Declares that the expected method may never be called. Ever!
atLeast()
Adds a minimum modifier to the next call count expectation. Thus atLeast()->times(3) means the call must be called at least three times (given matching method args) but never less than three times.
atMost()
Adds a maximum modifier to the next call count expectation. Thus atMost()->times(3) means the call must be called no more than three times. This also means no calls are acceptable.
between(min, max)
Sets an expected range of call counts. This is actually identical to using atLeast()->times(min)->atMost()->times(max) but is provided as a shorthand. It may be followed by a times() call with no parameter to preserve the APIs natural language readability.
ordered()
Declares that this method is expected to be called in a specific order in relation to similarly marked methods. The order is dictated by the order in which this modifier is actually used when setting up mocks.
ordered(group)
Declares the method as belonging to an order group (which can be named or numbered). Methods within a group can be called in any order, but the ordered calls from outside the group are ordered in relation to the group, i.e. you can set up so that method1 is called before group1 which is in turn called before method 2.
globally()
When called prior to ordered() or ordered(group), it declares this ordering to apply across all mock objects (not just the current mock). This allows for dictating order expectations across multiple mocks.
byDefault()
Marks an expectation as a default. Default expectations are applied unless a non-default expectation is created. These later expectations immediately replace the previously defined default. This is useful so you can setup default mocks in your unit test setup() and later tweak them in specific tests as needed.
mock()
Returns the current mock object from an expectation chain. Useful where you prefer to keep mock setups as a single statement, e.g.
$mock = \Mockery::mock('foo')->shouldReceive('foo')->andReturn(1)->mock();
引数の検証
The arguments passed to the with() declaration when setting up an expectation determine the criteria for matching method calls to expectations. Thus, you can setup up many expectations for a single method, each differentiated by the expected arguments. Such argument matching is done on a "best fit" basis. This ensures explicit matches take precedence over generalised matches.
An explicit match is merely where the expected argument and the actual argument are easily equated (i.e. using === or ==). More generalised matches are possible using regular expressions, class hinting and the available generic matchers. The purpose of generalised matchers is to allow arguments be defined in non-explicit terms, e.g. Mockery::any() passed to with() will match ANY argument in that position.
Here's a sample of the possibilities.
with(1)
Matches the integer 1. This passes the === test (identical). It does facilitate a less strict == check (equals) where the string '1' would also match the argument.
with(\Mockery::any())
Matches any argument. Basically, anything and everything passed in this argument slot is passed unconstrained.
with(\Mockery::type('resource'))
Matches any resource, i.e. returns true from an is_resource() call. The Type matcher accepts any string which can be attached to "is_" to form a valid type check. For example, \Mockery::type('float') checks using is_float() and \Mockery::type('callable') uses is_callable(). The Type matcher also accepts a class or interface name to be used in an instanceof evaluation of the actual argument.
You may find a full list of the available type checkers at http://www.php.net/manual/en/ref.var.php
with(\Mockery::on(closure))
The On matcher accepts a closure (anonymous function) to which the actual argument will be passed. If the closure evaluates to (i.e. returns) boolean TRUE then the argument is assumed to have matched the expectation. This is invaluable where your argument expectation is a bit too complex for or simply not implemented in the current default matchers.
with('/^foo/')
The argument declarator also assumes any given string may be a regular expression to be used against actual arguments when matching. The regex option is only used when a) there is no === or == match and b) when the regex is verified to be a valid regex (i.e. does not return false from preg_match()).
with(\Mockery::ducktype('foo', 'bar'))
The Ducktype matcher is an alternative to matching by class type. It simply matches any argument which is an object containing the provided list of methods to call.
with(\Mockery::mustBe(2));
The MustBe matcher is more strict than the default argument matcher. The default matcher allows for PHP type casting, but the MustBe matcher also verifies that the argument must be of the same type as the expected value. Thus by default, the argument '2' matches the actual argument 2 (integer) but the MustBe matcher would fail in the same situation since the expected argument was a string and instead we got an integer.
Note: Objects are not subject to an identical comparison using this matcher since PHP would fail the comparison if both objects were not the exact same instance. This is a hindrance when objects are generated prior to being returned, since an identical match just would never be possible.
with(\Mockery::not(2))
The Not matcher matches any argument which is not equal or identical to the matcher's parameter.
with(\Mockery::anyOf(1, 2))
Matches any argument which equals any one of the given parameters.
with(\Mockery::notAnyof(1, 2))
Matches any argument which is not equal or identical to any of the given parameters.
with(\Mockery::subset(array(0=>'foo')))
Matches any argument which is any array containing the given array subset. This enforces both key naming and values, i.e. both the key and value of each actual element is compared.
with(\Mockery::contains(value1, value2))
Matches any argument which is an array containing the listed values. The naming of keys is ignored.
with(\Mockery::hasKey(key));
Matches any argument which is an array containing the given key name.
with(\Mockery::hasValue(value));
Matches any argument which is an array containing the given value.
部分モックの作成
Partial mocks are useful when you only need to mock several methods of an object leaving the remainder free to respond to calls normally (i.e. as implemented).
Unlike other mock objects, a Mockery partial mock has a real concrete object at its heart. This approach to partial mocks is intended to bypass a number of troublesome issues with partials. For example, partials might require constructor parameters and other setup/injection tasks prior to use. Trying to perform this automatically via Mockery is not a tenth as intuitive as just doing it normally - and then passing the object into Mockery.
Partial mocks are therefore constructed as a Proxy with an embedded real object. The Proxy itself inherits the type of the embedded object (type safety) and it otherwise behaves like any other Mockery-based mock object, allowing you to dynamically define expectations. This flexibility means there's little upfront defining (besides setting up the real object) and you can set defaults, expectations and ordering on the fly.
Default Mock Expectations
Often in unit testing, we end up with sets of tests which use the same object dependency over and over again. Rather than mocking this class/object within every single unit test (requiring a mountain of duplicate code), we can instead define reusable default mocks within the test case's setup() method. This even works where unit tests use varying expectations on the same or similar mock object.
How this works, is that you can define mocks with default expectations. Then, in a later unit test, you can add or fine-tune expectations for that specific test. Any expectation can be set as a default using the byDefault() declaration.
Mocking Demeter Chains And Fluent Interfaces
Both of these terms refer to the growing practice of invoking statements similar to:
$object->foo()->bar()->zebra()->alpha()->selfDestruct();
The long chain of method calls isn't necessarily a bad thing, assuming they each link back to a local object the calling class knows. Just as a fun example, Mockery's long chains (after the first shouldReceive() method) all call to the same instance of \Mockery\Expectation. However, sometimes this is not the case and the chain is constantly crossing object boundaries.
In either case, mocking such a chain can be a horrible task. To make it easier Mockery support demeter chain mocking. Essentially, we shortcut through the chain and return a defined value from the final call. For example, let's assume selfDestruct() returns the string "Ten!" to $object (an instance of CaptainsConsole). Here's how we could mock it.
$mock = \Mockery::mock('CaptainsConsole'); $mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn('Ten!');
The above expectation can follow any previously seen format or expectation, except that the method name is simply the string of all expected chain calls separated by "->". Mockery will automatically setup the chain of expected calls with its final return values, regardless of whatever intermediary object might be used in the real implementation.
Arguments to all members of the chain (except the final call) are ignored in this process.
Mock Object Recording
In certain cases, you may find that you are testing against an already established pattern of behaviour, perhaps during refactoring. Rather then hand crafting mock object expectations for this behaviour, you could instead use the existing source code to record the interactions a real object undergoes onto a mock object as expectations - expectations you can then verify against an alternative or refactored version of the source code.
To record expectations, you need a concrete instance of the class to be mocked. This can then be used to create a partial mock to which is given the necessary code to execute the object interactions to be recorded. A simple example is outline below (we use a closure for passing instructions to the mock).
Here we have a very simple setup, a class (SubjectUser) which uses another class (Subject) to retrieve some value. We want to record as expectations on our mock (which will replace Subject later) all the calls and return values of a Subject instance when interacting with SubjectUser.
class Subject { public function execute() { return 'executed!'; } } class SubjectUser { public function use(Subject $subject) { return $subject->execute(); } }
Here's the test case showing the recording:
class SubjectUserTest extends extends PHPUnit_Framework_TestCase { public function teardown() { \Mockery::close(); } public function testSomething() { $mock = \Mockery::mock(new Subject); $mock->shouldExpect(function ($subject) { $user = new SubjectUser; $user->use($subject); }); /** * Assume we have a replacement SubjectUser called NewSubjectUser. * We want to verify it behaves identically to SubjectUser, i.e. * it uses Subject in the exact same way */ $newSubject = new NewSubjectUser; $newSubject->use($mock); } }
After the \Mockery::close() call in teardown() validates the mock object, we should have zero exceptions if NewSubjectUser acted on Subject in a similar way to SubjectUser. By default the order of calls are not enforced, and loose argument matching is enabled, i.e. arguments may be equal (==) but not necessarily identical (===).
If you wished to be more strict, for example ensuring the order of calls and the final call counts were identical, or ensuring arguments are completely identical, you can invoke the recorder's strict mode from the closure block, e.g.
$mock->shouldExpect(function ($subject) { $subject->shouldBeStrict(); $user = new SubjectUser; $user->use($subject); });
Dealing with Final Classes/Methods
One of the primary restrictions of mock objects in PHP, is that mocking classes or methods marked final is hard. The final keyword prevents methods so marked from being replaced in subclasses (subclassing is how mock objects can inherit the type of the class or object being mocked.
The simplest solution is not to mark classes or methods as final!
However, in a compromise between mocking functionality and type safety, Mockery does allow creating partial mocks from classes marked final, or from classes with methods marked final. This offers all the usual mock object goodness but the resulting mock will not inherit the class type of the object being mocked, i.e. it will not pass any instanceof comparison.
Mockery Global Configuration
To allow for a degree of fine-tuning, Mockery utilises a singleton configuration object to store a small subset of core behaviours. The two currently present include:
- Allowing the mocking of methods which do not actually exist
- Allowing the existence of expectations which are never fulfilled (i.e. unused)
By default, these behaviours are enabled. Of course, there are situations where this can lead to unintended consequences. The mocking of non-existent methods may allow mocks based on real classes/objects to fall out of sync with the actual implementations, especially when some degree of integration testing (testing of object wiring) is not being performed. Allowing unfulfilled expectations means unnecessary mock expectations go unnoticed, cluttering up test code, and potentially confusing test readers.
You may allow or disallow these behaviours (whether for whole test suites or just select tests) by using one or both of the following two calls:
\Mockery::getConfiguration()->allowMockingNonExistentMethods(bool); \Mockery::getConfiguration()->allowMockingMethodsUnnecessarily(bool);
Passing a true allows the behaviour, false disallows it. Both take effect immediately until switched back. In both cases, if either behaviour is detected when not allowed, it will result in an Exception being thrown at that point. Note that disallowing these behaviours should be carefully considered since they necessarily remove at least some of Mockery's flexibility.
Reserved Method Names
As you may have noticed, Mockery uses a number of methods called directly on all mock objects, for example shouldReceive(). Such methods are necessary in order to setup expectations on the given mock, and so they cannot be implemented on the classes or objects being mocked without creating a method name collision (reported as a PHP fatal error). The methods reserved by Mockery are:
- shouldReceive()
- shouldBeStrict()
In addition, all mocks utilise a set of added methods and protected properties which cannot exist on the class or object being mocked. These are far less likely to cause collisions. All properties are prefixed with "mockery" and all method names with "mockery".
Quick Examples
Create a mock object to return a sequence of values from a set of method calls.
class SimpleTest extends extends PHPUnit_Framework_TestCase { public function teardown() { \Mockery::close(); } public function testSimpleMock() { $mock = \Mockery::mock(array('pi' => 3.1416, 'e' => 2.71)); $this->assertEquals(3.1416, $mock->pi()); $this->assertEquals(2.71, $mock->e()); } }
Create a mock object which returns a self-chaining Undefined object for a method call.
use \Mockery as m; class UndefinedTest extends extends PHPUnit_Framework_TestCase { public function teardown() { m::close(); } public function testUndefinedValues() { $mock = m::mock('my mock'); $mock->shouldReceive('divideBy')->with(0)->andReturnUndefined(); $this->assertTrue($mock->divideBy(0) instanceof \Mockery\Undefined); } }
Creates a mock object which multiple query calls and a single update call
use \Mockery as m; class DbTest extends extends PHPUnit_Framework_TestCase { public function teardown() { m::close(); } public function testDbAdapter() { $mock = m::mock('db'); $mock->shouldReceive('query')->andReturn(1, 2, 3); $mock->shouldReceive('update')->with(5)->andReturn(NULL)->once(); // test code here using the mock } }
Expect all queries to be executed before any updates.
use \Mockery as m; class DbTest extends extends PHPUnit_Framework_TestCase { public function teardown() { m::close(); } public function testQueryAndUpdateOrder() { $mock = m::mock('db'); $mock->shouldReceive('query')->andReturn(1, 2, 3)->ordered(); $mock->shouldReceive('update')->andReturn(NULL)->once()->ordered(); // test code here using the mock } }
Create a mock object where all queries occur after startup, but before finish, and where queries are expected with several different params.
use \Mockery as m; class DbTest extends extends PHPUnit_Framework_TestCase { public function teardown() { m::close(); } public function testOrderedQueries() { $db = m::mock('db'); $db->shouldReceive('startup')->once()->ordered(); $db->shouldReceive('query')->with('CPWR')->andReturn(12.3)->once()->ordered('queries'); $db->shouldReceive('query')->with('MSFT')->andReturn(10.0)->once()->ordered('queries'); $db->shouldReceive('query')->with("/^....$/")->andReturn(3.3)->atLeast()->once()->ordered('queries'); $db->shouldReceive('finish')->once()->ordered(); // test code here using the mock } }