We’re big on mockery at Comic Relief. Usually it’s celebrities poking fun at themselves. Sometimes it’s for unit testing.
Prophets & revelations
There’s no getting away from the fact that Prophecy – the relatively young mocking framework bundled with PHPUnit since Feb 2015 – makes some odd choices with its terminology. But once you’ve been enlightened by the succinct and logical syntax offered by verbs like prophesize() and reveal(), it almost feels blasphemous to use anything else.
As well as being relatively new to PHPUnit itself, Prophecy is a bit buried below all the traditional mock syntax in the official docs. This is a real shame in my opinion, as Prophecy offers a bunch of advantages for anybody new to PHP testing – it should really be where every PHP mocking newbie starts in 2016.
So… what is it?
Prophecy is for mock objects (or ‘mocks’). These are fake versions of objects you’d normally use in your code. You define how the mocks will behave in certain circumstances, and then your tests can run with those fake objects instead of real ones.
This is essential for:
- Speed: We can’t wait while a database looks at thousands of users for every single test, every time we want to check if our changes are safe to merge.
- Isolation: I’d prefer not to wipe out the real database on every test either. Unit tests shouldn’t touch the DB at all.
How does it look?
Here’s a real example from a new service we’re working on:
// This sets $service up as a Prophecy based on our User service $service = $this->prophesize('Service\User'); // Now we can tell it to return a response given a fetchOne() call with a specific param $service->fetch($this->singleId)->willReturn(new Entity\User($this->data)); // We can now use our DI container to easily inject the mock service into a real controller $serviceMock = $service->reveal(); $this->controller = $this->app->getContainer()->get('userController'); $this->controller->setService($serviceMock);
With that setup done, our test is very simple indeed:
$fetchOneResponse = $this->controller->fetchOne($this->singleId); $this->assertInstanceOf('Entity\User', $fetchOneResponse); $this->assertEquals(new Entity\User($this->data), $fetchOneResponse);
Note that we’re testing the real fetchOne() method on our controller, which depends on the fetch() method of a service. It’s that service method which we’ve replaced with a mock version.
Didn’t we have this already?
Kinda! PHPUnit has had the concept of mocks since object-oriented PHP was a crazy new idea and clouds were still fluffy and free.
But using them was not exactly beautiful. As I see it, Prophecy does the same job but brings three great wins along with its strange linguistic choices: cleaner syntax, simpler behaviour, and better-isolated mocks by default.
Prophecy syntax is nice
This Drupal wiki page has a quick side-by-side that goes some way to explaining this, but hopefully the example above shows how your ‘prophetic’ mock object becomes a natural substitute for the real one.
Would your real code call fetchOne(123)? Then that’s exactly what your test setup does with the mock. Character for character, it’s the same. Just tag ->willReturn(…) on the end and you’re done – it couldn’t be much easier.
What’s more, the old approach leads you down a path where it’s tempting not to enforce specific method parameters – you can listen for any calls to a mock method, and then pass in arbitrary parameters. This can look appealing because the mock setup is shorter, easier and works fine – at first. But this becomes brittle and error-prone as soon as you call the same method twice in a test suite. Prophecy knows what’s best for you, and gives you a syntax where the easy thing to do is also the right thing.
Prophecy logic is simple
Hey, you said getString() should return ‘test’, then you didn’t even use it! Whaaaa!
…is the kind of thing I’m used to hearing from PHPUnit’s old mock framework. It’s a little needy.
It links assertions about exactly how much you expect to use each mocked method, with the places you define the methods.
This has a few problems:
- it makes mock setup for typical cases more verbose with no real benefit;
- it encourages you to define facts about method invocation alongside the method definitions – not with the relevant calls;
- by making this process annoying and error-prone, it encourages you to set every mocked method to ‘whatever’, and forget to actually check for invocations in those cases where they are important.
The normal approach with Prophecy doesn’t involve this kind of assertion when you mock a method.
But if you want to assert that a mocked method call will happen exactly once at part of your tests’ logic, go for it! Just give your prophet a shouldBeCalled() – either with the original method mock or later – and they’ll do the rest.
You set this up explicitly, at the logical point, and only when you want to – just like any other assertion. Easy.
The old mock builder is dangerous
While working on less brilliant projects than ours, I’ve seen supposed mock objects that actually do a ton of work with their real counterparts’ methods, because nobody remembered to mock them out or disable the real methods. Nasty hybrid objects containing both real and mocked behaviour are par for the course, and so it’s easy to make this mistake when you use them.
This can easily tie up your notionally slim, isolated unit tests with external dependencies, remote calls and even database operations, all stemming from what you thought was a well-contained mock object.
Prophecy has default behaviour that makes a mock a mock. If you didn’t mock the method, you can’t use it. Instead you’ll get a clear error that tells you what’s going on, so you can finish writing your test the right way.
Prophecy is the future
In my opinion these distinctions add up to a strong case for anyone new to PHP testing to use only Prophecy for mocks. It seems far more intuitive for new developers than the alternative syntax. And even if used to the old way, I don’t think it will take most developers long to switch to the different mental model.
We’re always keen to hear if we could be testing smarter though! Are there advantages to PHPUnit’s original mocking approach that I’ve missed? Do you find that Mockery or other external libraries have additional strengths – or is PHPUnit now all you could ever want? Let us know in the comments!
We’re working on cool new things in JS too! As we shift towards making a better user experience with React and other front-end technologies, we don’t want to lose sight of keeping the whole platform well tested.
We’re yet to finalise our ultimate strategy here, but there are a few neat ways to bring mocks into your JS tests, depending mostly on any existing framework choices.
Jasmine has a general concept of ‘spies’ which can work like mocks for some unit tests. AngularJS (1) had its own approach, closely tied to its module loader – a bit tightly-coupled for my liking. Jest does it by jumping onto existing requires, which is a more widely applicable strategy.
And React has its own options that play particularly well with its model for components: the official ReactTestUtils and the Enzyme library both look promising for testing components’ behaviour, and mocking out the parts you’re not looking at.
For more on Prophecy, I would recommend:
- Prophecy’s own intro to its key concepts & usage
- The PHPUnit Prophecy documentation
- Comparison with PHPUnit mocks on Drupal.org – Prophecy’s syntax vs. traditional mocks side-by-side
- Conceptual difference between Mockery and Prophecy