phpspec/prophecy
Highly opinionated yet flexible PHP mocking framework for unit tests. Create test doubles with a Prophet, define expected calls and return values, then reveal mocks and verify predictions. Works with PHPUnit and other frameworks; requires PHP 7.2+.
Start by installing phpspec/prophecy via Composer in your require-dev section:
composer require --dev phpspec/prophecy
Initialize a Prophet instance—typically in your test class setUp()—and use prophesize() to create object prophecies:
$prophet = new \Prophecy\Prophet;
$mock = $prophet->prophesize(SomeInterface::class);
Then reveal the double with reveal() and inject it into your SUT. Finally, call checkPredictions() (e.g., in tearDown()) to assert that expected calls occurred. This minimal workflow enables expressive, behavior-focused testing even with PHPUnit.
Stubbing behavior: Define return values or side effects using method prophecies:
$service->process(Argument::type(User::class))->willReturn(true);
Use willReturn($v1, $v2, ...) for sequential calls with identical signatures.
Dynamic behavior via callbacks: Use will() with a closure to model state changes or interdependencies:
$user->setName(Argument::type('string'))->will(function ($args) {
$this->name = $args[0];
return $this;
});
Argument wildcards: Leverage Argument::type(), Argument::exact(), Argument::containingString(), etc., to avoid brittle exact-match assertions. Combine specificity (e.g., type()) and generality (any()) strategically—the more precise token wins in ambiguous matches.
** mocks vs stubs**: Use shouldBeCalled() / shouldNotBeCalled() or custom predictions (e.g., times(2)) to assert interactions. Use shouldBeCalledOnce() or times() helpers for brevity. Always pair mocks with checkPredictions().
Reveal early, verify late: Reveal doubles early in the test for injection, but defer prediction checks to tearDown() or explicit assert() calls to ensure all interactions are captured.
Dummies vs stubs: Calling undefined methods on a stub throws UnexpectedCallException. For stubs, all public method calls must be prophesized. Dummies (from a prophecy without promises) allow any call and return null.
Idempotency of method prophecies: $mock->foo(1) returns the same MethodProphecy instance on repeated calls with identical arguments. Modifying it later (e.g., chaining shouldNotBeCalled()) affects all prior usages.
Argument token matching: Tokens are scored and the highest-score match wins. Using Argument::any() in multiple overlapping prophecies can silently override more precise ones—prefer precise tokens (type(), which()) and order carefully.
Custom predictions: Prophecy lacks built-in times() in phpspec/prophecy v1.x (despite docs); it’s only available via phpspec/prophecy extensions like phpspec/prophecy-phpunit. Consider installing phpspec/prophecy-phpunit or define custom predictions:
$mock->foo()->shouldHaveBeenCalledTimes(2);
Revealed objects are proxies: The double’s reveal() result is a runtime-generated proxy class. Functions like get_class() or instanceof work, but debug_backtrace() may be misleading—avoid relying on internal class names.
Failing predictions don’t halt execution: Predictions are checked only on checkPredictions(); calls may proceed normally before verification. This avoids early-exit issues but means assertions only surface at test teardown.
Extensibility: Extend with custom TokenInterface or PromiseInterface/PredictionInterface implementations for domain-specific patterns (e.g., Argument::like($regex) or CallWithSideEffectPromise).
How can I help you explore Laravel packages today?