phpspec/prophecy: PHP mocking through object prophecies
Highly opinionated mocking framework for PHP 5.3+
At a glance
- What is it?
- Prophecy is an MIT-licensed PHP mocking framework built around the Prophet, ObjectProphecy and MethodProphecy objects. It suits teams that want to describe future behaviour of collaborators instead of recording expectations up front, and it assumes PHP 7.2 or greater.
- Who is it for?
- Adopt Prophecy if your tests need doubles that are described rather than recorded, and if you are comfortable with the Prophet and ObjectProphecy vocabulary. Skip it if you want a mocking API that mirrors PHPUnit's own, or if you need to verify that a method was never called, because the README documents only positive predictions.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 169 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Prophecy targets: describing collaborators before they exist
Test doubles usually get written in one of two directions. You either record what the code under test did to a collaborator and assert on that recording afterwards, or you state in advance how the collaborator will behave and let the test fail when reality diverges. Prophecy takes the second direction and builds its whole vocabulary around it. The README is explicit that the library "concentrates on describing the future behavior of objects with very limited knowledge about them", and that framing explains almost every API decision in the package.
The audience is PHP developers who already run a test framework and want a double that does not need the real class to be instantiated. The README states the library was created to fulfil phpspec2 needs but is flexible enough to be used inside any testing framework with minimal effort. In practice that means PHPUnit users are the second audience: the README's own opening example is a class extending PHPUnit\Framework\TestCase. If you write tests for services that talk to a hasher, a session handler or an HTTP client, and you would rather assert on the final state of your own object than on a call log, this is the shape of tool you are looking for.
Prophet, ObjectProphecy, MethodProphecy: the three objects in the chain
Everything starts with a Prophet. The README describes it as the thing that creates prophecies by prophesizing them, and the code is a single call: new Prophecy\Prophet. From the Prophet you call prophesize(), which returns an ObjectProphecy. That object is not yet a usable double. It is a description of the double you want, and you can constrain it with willExtend('stdClass') or willImplement('SessionHandlerInterface'). PHP allows multiple interfaces but only one parent class, and the README notes this constraint applies to the prophecy too.
The double itself appears when you call reveal() on the ObjectProphecy. The README calls the result a dummy: an object that extends or implements the preset types and overrides all their public methods, with every method returning null and never throwing. A dummy is deliberately logic-free. If you need behaviour you keep working with the ObjectProphecy, not the dummy. That split is the part new users trip over, because the variable holding the dummy looks like the thing you should be configuring.
Behaviour is attached by making an arbitrary call on the ObjectProphecy. $prophecy->read('123') returns a MethodProphecy, and calling willReturn('value') on it is a shortcut for will(new Prophecy\Promise\ReturnPromise(array('value'))). The README lists the built-in promises: ReturnPromise, ReturnArgumentPromise, ThrowPromise and CallbackPromise, plus the ability to add more by implementing Prophecy\Promise\PromiseInterface.
One design decision deserves attention. Prophecy enforces idempotency for method prophecies: two calls to $prophecy->read('123') return the same MethodProphecy object, while $prophecy->read('321') returns a different one. The README presents this as a feature, and it is, because it means a promise attaches to a call signature rather than to a line of code. The cost is that you cannot stack two different promises on the same signature and expect both to survive. Where PHPUnit or Mockery users would reach for call-count predictions, the README directs them to promises that mutate other promises from inside a callback.
Installing phpspec/prophecy and writing a first test
The README states the prerequisite plainly: PHP 7.2.0 or greater. Installation goes through Composer. Add the package to require-dev in composer.json, then run composer install. The README's own snippet uses the constraint ~1.0, and the newest release listed is v1.26.0.
{
"require-dev": {
"phpspec/prophecy": "~1.0"
}
}$> composer install --prefer-distWith the package present, the README's example test is the fastest way to see the whole chain. It creates a Prophet in setUp(), prophesizes a Hasher, reveals it, and passes the revealed double into a real User entity. The expectation is stated before the code under test runs.
<?php
class UserTest extends PHPUnit\Framework\TestCase
{
private $prophet;
public function testPasswordHashing()
{
$hasher = $this->prophet->prophesize('App\Security\Hasher');
$user = new App\Entity\User($hasher->reveal());
$hasher->generateHash($user, 'qwerty')->willReturn('hashed_pass');
$user->setPassword('qwerty');
$this->assertEquals('hashed_pass', $user->getPassword());
}
protected function setUp()
{
$this->prophet = new \Prophecy\Prophet;
}
protected function tearDown()
{
$this->prophet->checkPredictions();
}
}What you should see is a passing assertion on 'hashed_pass'. The tearDown() call to checkPredictions() is the part people forget, and it is the only thing that turns a stated promise into something the test enforces. Without it the promise is inert. Note also that the README's example uses the pre-PHPUnit 8 setUp() signature with no return type; on newer PHPUnit versions you will need to match the parent signature or PHP will complain.
Where Prophecy gets in the way
The idempotency rule is the sharpest edge. Because the same method call with the same arguments always returns the same MethodProphecy, a second willReturn() on that signature overwrites the first rather than adding a second behaviour. The README shows the intended workaround, a callback that calls setName('everzet')->will(function () { $this->getName()->willReturn('everzet'); }), but that pattern is harder to read than a simple call-count expectation and it hides the sequencing inside a closure.
Prediction coverage is the second gap. The README discusses checkPredictions() and method predictions, but the documentation does not describe a negative counterpart such as asserting that a method was never called. If your test strategy depends on proving absence of an interaction, you will be writing that check by hand or reaching for a different tool.
The third issue is version drift. The README's installation section still shows a PHPUnit example using setUp() without a return type, which is not valid on current PHPUnit releases. The library itself requires PHP 7.2.0 or greater, so the documentation and the runtime have moved at different speeds. That is a documentation problem, not a library problem, but it costs time on the first day.
Finally, Prophecy is the wrong tool when the collaborator is trivial. If a class has one method returning a constant, a hand-written test double in a few lines is clearer than a Prophet, an ObjectProphecy and a reveal. The abstraction pays off when the collaborator has several methods and you need to state several behaviours.
Prophecy against PHPUnit's own mocking API
The obvious alternative is PHPUnit's built-in createMock() and getMockBuilder(), which ship with the framework. The difference is directional. PHPUnit builds a double and then you configure it with method() and expects(), which is a recording API: you say how many times a call should happen and what it returns, and the framework checks the count afterwards. Prophecy inverts that. You state what the method will return, and checkPredictions() verifies that the stated promises were fulfilled. There is no call-count argument in the Prophecy API as documented.
The practical consequence is that PHPUnit's API reads like a specification of interactions and Prophecy's reads like a specification of behaviour. Teams that already think in terms of "this method is called twice" will find PHPUnit more direct. Teams that think in terms of "when asked for a hash, return this" will find Prophecy's MethodProphecy chain shorter.
A second difference is the dummy. Prophecy separates the dummy (revealed, inert) from the prophecy (configurable), which lets you pass a type-correct object into a constructor before you have decided anything about its behaviour. PHPUnit's mocks do not draw that line as explicitly. The README's own framing, that a dummy is not a prophecy, is worth taking literally.
Maintenance, releases and the MIT licence
The repository is not archived. The last push was on 2026-04-13, which is within six months of the current date, and the release history shows v1.26.0 on 2026-02-24, v1.25.0 on 2026-02-09 and v1.24.0 on 2025-11-21. Three releases inside roughly four months is a steady cadence rather than a burst. The repository carries a phpstan-baseline.neon and a phpstan.dist.neon, a .php-cs-fixer.dist.php and a phpunit.xml.dist, so static analysis and code style are part of the build rather than an afterthought. A spec/ directory sits alongside tests/, which is consistent with the project's phpspec origins.
The upgrade cost is low by design. The package is a dev dependency, so it never reaches production, and the README's constraint example (~1.0) allows minor updates within the 1.x line. The CHANGES.md file at the repository root is where breaking changes would be listed; the repository does not describe its contents, so read it before moving across a minor version. The main upgrade risk is not the library but the surrounding test framework, since the README's PHPUnit example predates current setUp() signatures.
The licence is MIT. In practical terms that permits use, modification and redistribution with the licence text retained, including in closed-source projects. This is a description of the licence identifier, not legal advice; if your organisation has a policy on third-party dev dependencies, route it through that policy rather than through this paragraph.
Editorial conclusion
Adopt Prophecy if your tests need doubles that are described rather than recorded, and if you are comfortable with the Prophet and ObjectProphecy vocabulary. Skip it if you want a mocking API that mirrors PHPUnit's own, or if you need to verify that a method was never called, because the README documents only positive predictions. Before committing, wire checkPredictions() into tearDown(), confirm your PHP version is at least 7.2.0, and check that the version constraint you pick resolves against the v1.26.0 release line.
Frequently asked questions
What PHP version does phpspec/prophecy require?
The README states that Prophecy requires PHP 7.2.0 or greater. That is listed under the Prerequisites heading in the installation section.
How do I install phpspec/prophecy?
Add phpspec/prophecy to the require-dev list in composer.json and run composer install --prefer-dist. The README shows the constraint ~1.0 in its own example.
What is the difference between a dummy and a prophecy in phpspec/prophecy?
A prophecy is the ObjectProphecy you configure, while the dummy is what reveal() returns: an object that extends or implements the preset types, overrides all public methods, returns null and never throws. The README states that a dummy is not a prophecy and that you should keep working with the prophecy to set expectations.
Why does phpspec/prophecy return the same MethodProphecy for the same call?
Prophecy enforces idempotency, so the same method call with the same arguments always returns the same MethodProphecy, while a different argument set returns a different one. The README presents this as intended behaviour and suggests using promises with callbacks for more complex sequencing.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/phpspec-prophecy)