Mocking

phpspec 9 has a built-in mock system — no Prophecy required. Create test doubles, stub return values, and verify method calls with a clean, fluent API.

Creating mocks

mock() creates a test double that implements the given interface or extends the class:

$logger = mock(App\Logger::class);
expect($logger)->toBeAnInstanceOf(App\Logger::class);

Stubbing return values

allow()->toReturn()

Stub a method to return a fixed value:

$repo = mock(App\UserRepository::class);
allow($repo->find(1))->toReturn('Alice');

expect($repo->find(1))->toBe('Alice');

allow()->toReturnUsing()

Stub with a callback for dynamic returns:

allow($repo->find(1))->toReturnUsing(
    fn($id) => $id === 1 ? 'Alice' : null
);

Argument-based dispatch

Different arguments return different values:

allow($repo->find(1))->toReturn('Alice');
allow($repo->find(2))->toReturn('Bob');

expect($repo->find(1))->toBe('Alice');
expect($repo->find(2))->toBe('Bob');

Catch-all stubs

Call allow() without arguments for a catch-all, then add specific overrides:

allow($repo->find())->toReturn('default');
allow($repo->find(42))->toReturn('special');

expect($repo->find(42))->toBe('special');
expect($repo->find(99))->toBe('default');

Verifying method calls

toBeCalled()

$logger = mock(App\Logger::class);
expect($logger->log('hello'))->toBeCalled();
$logger->log('hello');

toBeCalledWith()

expect($logger->log('hello'))->toBeCalledWith('hello');
$logger->log('hello');

toBeCalledTimes()

$counter = mock(App\Counter::class);
expect($counter->increment())->toBeCalledTimes(3);
$counter->increment();
$counter->increment();
$counter->increment();

Fluent call count

// Exactly once
expect($counter->increment())->toBeCalled()->once();

// Exactly N times
expect($counter->increment())->toBeCalled()->exactly(3)->times();

// Never called
expect($logger->log('anything'))->not()->toBeCalled();

Argument matchers

MatcherDescription
any()Matches any single argument
type('string')Matches argument of given type
callback(fn)Matches if callback returns true
anInstanceOf(Class)Matches instanceof
cetera()Matches all remaining arguments
noArgs()Matches when called with no arguments
// any() — matches any value
expect($logger->log('msg'))->toBeCalledWith(any());

// anInstanceOf() — matches class
expect($bus->dispatch(new Event('test')))
    ->toBeCalledWith(anInstanceOf(Event::class));

// cetera() — matches trailing args
expect($logger->log('info', 'msg'))
    ->toBeCalledWith('info', cetera());

Argument matchers also work in stub setup:

allow($repo->find(any()))->toReturn('fallback');
allow($repo->find(42))->toReturn('special');

Type-hinted mock injection

Type-hint parameters in it() or let() closures to auto-inject mocks:

// Mock injected via it()
it('injects mocks by type hint', function (Mailer $mailer) {
    expect($mailer)->toBeAnInstanceOf(Mailer::class);
});

// Mock injected via let()
let('counter', fn(App\Counter $counter) => $counter);

Any parameter with an interface or class type hint is automatically resolved via mock().

Fresh mocks per example

let() values are re-evaluated for every example, so each example gets a fresh mock with no recorded calls:

describe('let reset', function() {
    let('counter', fn(App\Counter $counter) => $counter);

    it('first example calls increment', function() {
        expect($this->counter->increment())->toBeCalled()->once();
        $this->counter->increment();
    });

    it('second example gets fresh mock', function() {
        expect($this->counter->increment())->not()->toBeCalled();
    });
});

London School example

Mock collaborators and test in isolation:

use App\UserService;
use App\UserRepository;

describe(UserService::class, function () {
    it('returns the display name', function (UserRepository $repo) {
        allow($repo->find(1))->toReturn(['name' => 'Alice']);
        $service = new UserService($repo);
        expect($service->getDisplayName(1))->toBe('Alice');
    });

    it('returns Unknown for missing user', function (UserRepository $repo) {
        allow($repo->find(999))->toReturn(null);
        $service = new UserService($repo);
        expect($service->getDisplayName(999))->toBe('Unknown');
    });
});