Spies have been on the cards for mockery for a long time and even after putting together an implementation in February, I kind of stalled out on making a decision on the public API. Fast forward a few months and I figured it was just time to ship it, so I went with the most mockery like API and merged it in. Mockery still doesn't have a 1.0 release, so I can always make changes before we go 1.0.
What is a Spy?
When I talk about spies, I use the definition from the book xUnit Test Patterns, that is:
Use a Test Double to capture the indirect output calls made to another component by the system under test (SUT) for later verification by the test.
Like mocks, we replace one of the SUTs dependencies with a spy. The spy then records any interaction between it and the SUT, allowing us to make assertions against those interactions later.
Revealing intent
There are a few technical reasons why you might use a spy, but the most important to me isn't so technical, and that is how spies can help reveal the intentions of a test.
As opposed to mocks, spies are configured to ignore all interactions while the SUT is being exercised, like a null object. Effectively equivalent to this with mockery:
$dispatcher = Mockery::mock("EventDispatcherInterface")->shouldIgnoreMissing();
This means that you don't have to set up expectations for every method call the double might receive during the test, some of which may not be relevant to the current test. A spy allows us to make assertions about the calls we care about for this test only, reducing the chances of over-specification and making our tests more clear.
Spies also allow us to follow the more familiar Arrange-Act-Assert or Given-When-Then style within our tests. With mocks, we have to follow a less familiar style, something a long the lines of Arrange-Expect-Act-Assert, where we have to tell our mocks what to expect before we act on the sut, then assert that those expectations where met.
/** @test */
function it_fires_the_on_request_event()
{
// arrange
$dispatcher = Mockery::mock("EventDispatcherInterface");
$sut = new Kernel($dispatcher);
// expect
$dispatcher->shouldReceive("dispatch")
->with("onRequest", m::type("Event"))
->once();
// act
$sut->handle(new Request());
// assert
Mockery::close();
}
Spies allow us to skip the expect part and move the assertion to after we have acted on the SUT, usually making our tests more readable.
/** @test */
function it_fires_the_on_request_event()
{
// arrange/given
$dispatcher = Mockery::spy("EventDispatcherInterface");
$sut = new Kernel($dispatcher);
// act/when
$sut->handle(new Request());
// assert/then
$dispatcher->shouldHaveReceived("dispatch")
->with("onRequest", m::type("Event"));
}
So where's the trade-off?
At first glance, spies sound like a far better alternative to mocks, and for the style of code I write, they usually are, however they do have some drawbacks.
The first one is that spies are far less restrictive than mocks, meaning tests are usually less precise, as they let you get away with more. This is usually a good thing, they should only be as precise as they need to be, but while spies make our tests more intent-revealing, they do tend to reveal less about the design of the SUT. If you're having to setup lots of expectations for a mock, in lots of different tests, your tests are trying to tell you something. We don't get this with spies, they simply ignore the calls that aren't relevant to them.
Another less obvious downside to using spies is debugging. When a mock receives a call that it wasn't expecting, it immediately throws an exception (failing fast), giving you a nice stack trace or possibly even invoking your debugger. With spies, we're simply asserting calls were made after the fact, so if the wrong calls were made, we don't have quite the same just in time context we have with the mocks.
Given these drawbacks, it might be worthwhile considering mocks for design, then refactoring to spies to make the tests we're are left with more readable.
Partial Spies
Mockery Spies are just like the usual mockery doubles, in that you can make them act as partial doubles, deferring calls to the original class, acting as if you were simply using a normal object of that type. Partial doubles are a little controversial for the TDD crowd, if you find yourself needing a partial, that should usually be considered a test smell and you should be reconsidering your design. For this reason, phpspec's prophecy doesn't support them. Mockery does though, as we try to cater for a wider crowd, and partial spies can be a nice compromise for those who prefer the state-based approach to testing with real components, where it's hard to verify something happened based on the final state of the SUT.
$cache = Mockery::spy("Cache")->makePartial();
// act on SUT that uses $cache
$cache->shouldHaveReceived("load")->with("a.cache.key");
That's all for now, I've not stamped a release yet, so you'll need to be working against the master branch if you want to give them a try. Docs are on the way, checkout the tests for now.