Dummies, method prophecies, and promises: how phpspec/prophecy builds a PHP double
Highly opinionated mocking framework for PHP 5.3+
At a glance
- What is it?
- Prophecy is a small object mocking library built on three moving parts: a Prophet hands out ObjectProphecy objects, reveal() returns a dummy that answers null to everything, and MethodProphecy promises decide what a stubbed call returns.
- Who is it for?
- Prophecy fits PHPUnit and phpspec suites that need a token object to satisfy a typehint plus a few stubbed return values, and it fits them best when the interaction itself is not the assertion. Before adopting it, check the PHP version on every host against the 7.2.0 requirement.
- 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?
- Activity is slowing. The repository last received commits 7 months 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- phpspec/prophecy8,462 ★ · -5
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| phpspec/prophecy | 8.5k | 8.5k |
-5 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
reveal() returns a different object from the one you configure
What you configure and what you inject are two objects. You configure an ObjectProphecy held in `$prophecy`, and `reveal()` gives you a dummy: a generated object that extends the class named by `willExtend()` or implements the interface named by `willImplement()`. PHP lets an object implement several interfaces but extend only one parent class, and those two calls are where that limit shows up.
A dummy overrides every public method of the type it imitates and carries no logic. Any method returns `null`, and nothing throws. That is what you want when production code needs a token to satisfy a typehint, and it is not what you want when the test needs behavior. Keep the two apart in your own code: the dummy goes into the class under test, the prophecy stays in the test where promises get attached.
One method prophecy per method and argument pair, enforced
Calling the same method with the same arguments twice hands back the identical object, and a different argument list hands back a different one.
$methodProphecy1 = $prophecy->read('123');
$methodProphecy2 = $prophecy->read('123');
$methodProphecy3 = $prophecy->read('321');
$methodProphecy1 === $methodProphecy2;
$methodProphecy1 !== $methodProphecy3;Because the pair is the key, a second promise for `read('123')` lands on the same MethodProphecy and answers for that pair, which is the whole trick behind a stateful double. The example declares `getName()` as returning `null`, then hands `setName('everzet')` a callback that reassigns `getName()` to return `everzet`. Same pair, later answer, and the new value sticks for every later call in any order. The price of that consistency is that you can never hold two different answers for one method with one argument list, so a case that needs both has to be expressed as a promise that decides at call time.
Promises describe what a call returns, never whether it happens
Where PHPUnit and Mockery let you predict how many times a method will be called in order to make one call change the behavior of others, the documented approach here is a promise, followed by the statement that it does not matter how many times or in which order your methods are called. That statement is about stubbed behavior, not about verification: method prophecies can also carry predictions, which the README covers in its Mocks section.
A dummy answers null forever and cannot raise the exception you wanted
Two rules define every dummy: all public methods return `null`, and no method ever throws. The README states both halves, and the design is sound for a token object.
It is also why a half finished collaborator can pass a test. The method you forgot to stub still answers, with `null`, so the test runs to the end instead of stopping at the missing piece. The sharper edge is failure. If the code under test is meant to reject a bad password, raise a validation error, or report a network fault, a dummy cannot produce any of it, so an assertion wrapped around that exception means nothing until you attach `ThrowPromise` or the `->willThrow($exception)` shortcut to that exact method and argument list. A test that expects a failure it never stubbed is not checking the failure at all. Go through each double in a new test and confirm a promise exists for every call you expect to blow up.
The PHP 5.3 label sits beside a 7.2.0 requirement
The repository describes Prophecy as a highly opinionated mocking framework for PHP 5.3+, while the prerequisites section requires PHP 7.2.0 or greater. Treat the 7.2.0 floor as the real constraint and the description as historical packaging.
The README still carries every closure style in a single example. One branch is marked For PHP 5.4 and binds `$this` to the prophecy inside the callback. A second branch is marked For PHP 5.3 and receives `$args` plus the prophecy as parameters. A third captures the prophecy with `use`. Those branches record what the library once supported, not what it promises your runtime. Check the version first, because the dependency entry itself warns you about nothing: it carries only a `~1.0` constraint and no platform requirement to trip over.
Composer wires the Prophet into require-dev, not into your runtime
Prophecy is declared as a development dependency, so the Prophet only exists where tests run.
{
"require-dev": {
"phpspec/prophecy": "~1.0"
}
}$> composer install --prefer-distFrom there the path is short and fixed. Build the Prophet, ask it for a prophecy, name the type, then reveal the dummy:
$prophet = new Prophecy\Prophet;$prophecy = $prophet->prophesize();$prophecy->willExtend('stdClass');
$prophecy->willImplement('SessionHandlerInterface');$dummy = $prophecy->reveal();Inside a PHPUnit case the Prophet is built in `setUp()`, and `prophesize()` takes the class name as a string, so one Prophet serves every test method in the case.
PromiseInterface is the one seam where you can add your own behavior
Custom behavior enters through `MethodProphecy::will(PromiseInterface $promise)`, and four implementations ship with the library. `ReturnPromise` returns a fixed value, `ReturnArgumentPromise` returns the nth argument of the call, `ThrowPromise` raises an exception, and `CallbackPromise` runs your own logic. Any new promise implements `Prophecy\Promise\PromiseInterface`.
The shortcuts are the same mechanism in shorter form, and one call in particular is written both ways, since `willReturn('value')` is just a shortcut for a `ReturnPromise` built around the same value.
$prophecy->read('123')->willReturn('value');$prophecy->read('123')->will(new Prophecy\Promise\ReturnPromise(array('value')));So plan the behavior of a double around promises and treat everything else as fixed, because PromiseInterface is the extension point the README names. The tree holds `fixtures/`, `spec/`, `tests/`, `phpstan.dist.neon`, `phpstan-baseline.neon`, `.php-cs-fixer.dist.php`, `CHANGES.md` and `CONTRIBUTING.md`, and the last push on master landed on 2026-04-13, after v1.26.0 on 2026-02-24, v1.25.0 on 2026-02-09 and v1.24.0 on 2025-11-21.
Editorial conclusion
Prophecy fits PHPUnit and phpspec suites that need a token object to satisfy a typehint plus a few stubbed return values, and it fits them best when the interaction itself is not the assertion. Before adopting it, check the PHP version on every host against the 7.2.0 requirement.
Frequently asked questions
Does phpspec/prophecy still run on PHP 5.3?
The prerequisites section requires PHP 7.2.0 or greater, even though the repository description names PHP 5.3+. The README keeps both closure styles in one example, one branch marked For PHP 5.4 and one marked For PHP 5.3, as a record of older support.
What is the difference between a Prophecy dummy and an ObjectProphecy?
The ObjectProphecy stays in your test and carries the promises. The dummy comes out of reveal(), extends or implements the type you named, returns null from every public method, and is the object you pass to the code under test.
Can phpspec/prophecy assert that a method was called exactly once?
Promises do not: they only decide what a call returns. The README says method prophecies can also carry predictions, which it covers in its Mocks section.
How do I make a mocked method throw an exception in phpspec/prophecy?
Attach ThrowPromise, or the ->willThrow($exception) shortcut, to the method prophecy for that exact method and argument list, because a dummy by itself returns null and never throws.
Why does my phpspec/prophecy test pass when I forgot a willReturn call?
A dummy overrides all its public methods and answers null from every one of them. Until you attach a promise, the unstubbed method still returns a value, so the test keeps running instead of failing on the missing expectation.
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)Star history chart for your README
[](https://hysenlabs.com/projects/phpspec-prophecy)Was this useful?