A common concern that gets raised about using test doubles (mocks, stubs, spies etc) , is that of the configuration of the test double stubs or expectations being out of sync with the signatures of the actual type. Probably best explained with an example.

interface UserRepository { }

class Foo 
{
    /* ... */

    function bar()
    {
        $this->userRepository->delete(123);
    }
}

/** @test */
function should_delete_user_123()
{
    $userRepository = Mockery::mock(UserRepository::class); 
    $userRepository->shouldReceive("delete")->with(123)->once();

    $foo = new Foo($userRepository);

    $foo->bar();
}

Despite the delete method not existing on the UserRepository interface, this test will pass. For Mockery, this is by design. When I'm in my TDD loop, , I'm designing the UserRepository interface as I develop the Foo service, programming by wishful thinking. In order for Foo to do bar, I'd like to assume I have a UserRepository::delete method, but I'll care about adding that later. The problem manifests when we don't necessarily remember to deal with it later. We should notice the problem when we run some higher level test, but they don't always exist and even if they do, we might make the mistake of adding the delete method to the concrete UserRepository used in that higher level test, rather than the abstract. All the tests will pass, but things still wont be quite right.

The ruby community came up with a solution to this, rspec-fire, which was subsequently made obselete in favour of verifying-doubles in rspec core. This works by allowing you to program by wishful thinking, until you actually create the class, at which point rspec will check to make sure the method you are stubbing or expecting actually exists. Rspec will also check the arity of the stubs and expectations against the real thing.

This sounds great, but kind of annoys me. Just because a class or type exists, doesn't mean it's API is finalised. I would prefer to continue programming by wishful thinking within my TDD loop.

So how are we going to deal with this problem in PHP? For methods that don't exist at all, Prophecy will throw exceptions if you try to set up stubs or expectations.

There was 1 error:

1) ProphecyTest::prophecy_test
Prophecy\Exception\Doubler\MethodNotFoundException: Method `Double\UserRepository\P1::update()` is not defined

I don't believe PHPUnit mocks has any such feature at present, but it looks like something is in the works.

Mockery comes with a global configuration option to prevent stubbing and expecting methods that don't exist yet, that is disabled by default. You can turn it on in your test bootstrap. I like to turn it on for test runs outside the TDD loop. These test runs are more looking for regressions like on a CI server, rather than helping me develop behaviour in the system, so it seems sensible to verify we aren't doing anything stupid. I get to ignore warnings during my TDD loop, but get the safety net of having them verified at a later date.

Mockery::getConfiguration()->allowMockingNonExistentMethods(false);

As for the arity of existing methods, I think that's a problem best solved with the proper use of PHP's type hints.