#641·prophecy

[RFC] Introduce different mode of operation for our doubles

Author: stofCreated May 1, 2025Updated May 1, 2025

prophecy-phpunit does not allow creating doubles in data provider because those must be static while prophesize is an instance method. Being an instance method is necessary because mocks must be associated to a test so that their predictions are verified at the end of the test. Comparing this to PHPUnit mock objects, they have 2 different APIs. The method createMock is an instance method (for the same reason that our method) but createStub is static.

I suggest introducing 3 modes of operations for the ObjectProphecy:

  • stub mode: MethodProphecy::should and MethodProphecy::shouldHave throw an exception as defining or verifying predictions on a stub is forbidden. Object prophecies created in stub mode are not registered in the Prophet::$prophecies property. Unexpected calls to a stub throw immediately (see #528 for justification, see #508 and #472 as well). The existing special dummy-like behavior allowing any method call when there is no expectation will be gone (maybe only deprecated at first as discussed in #528, to be checked). As an optimization, we could even make ObjectProphecy::findProphecyMethodCalls throw for stubs (it is used only for checking predictions) which then allows the CallCenter to skip registering all calls in its recordedCalls property.
  • mock mode: MethodProphecy::shouldHave throws an exception. Unexpected calls to a mock throw immediately (see #528, solves #508)
  • spy mode: the existing mode where unexpected calls are reported when verifying predictions at the end of the test

Maybe the mock and spy modes can be combined into a single mode based on the suggestion done in #528:

  • unexpected method calls for void-returning methods are reported when verifying predictions
  • other unexpected methods calls are reported immediately
  • spying on a method that is not returning void (this includes methods that have no return type) involves allowing the call by using one of the will method to define the behavior of the method (solving the case that it currently returns null even though the phpdoc makes that an unexpected type)

This combined approach has a DX drawback for projects that don't rely on spies at all: reporting unexpected method calls immediately makes it a lot easier to debug the issue, because the unexpected call itself (and its call stack) is part of the stack trace of the UnexpectedCallException. On the other hand, it allows to keep our API simple, by having prophesize return full-featured object prophecies as done today (while stricter based on #528) and prophesizeStub returning a stub ObjectProphecy.