skip to content

In PHPUnit 13, what is the difference between createStub() and createMock(), and how do you choose between them?

level: middleimportance: must knowfreq 64%

answer

  1. canned answers versus checked calls
  2. Stub interface: only method()
  3. MockObject adds expects()
  4. createConfiguredStub: method name => value
  5. notice when a mock has no expectations

basics

~20 s

createStub() returns a Stub that only answers calls with configured values; createMock() returns a MockObject that can also carry expects() rules that PHPUnit enforces. Stub collaborators that feed data in, mock those whose calls are the outcome.

solid answer

~50 s

Both factories generate a subclass or implementation of the type you pass, skip the real constructor and return defaults for anything you leave unconfigured. `createStub()` returns a `Stub`, whose only API is `method()` followed by `willReturn()`, `willReturnMap()`, `willReturnCallback()` or `willThrowException()`. `createMock()` returns a `MockObject`, which adds `expects($this->once())` and, after it, `with()`; wrong arguments and extra calls fail at the call, missing calls fail when PHPUnit verifies the mock after the test method, and verified mocks count as assertions. In a sign-up service test I stub the user repository, because it only supplies data, and mock the mailer, because sending the welcome mail is the behaviour under test. PHPUnit 13 enforces the split: a mock that never gets an expectation triggers a notice telling you to use a stub, unless the test opts out with `#[AllowMockObjectsWithoutExpectations]`.

code

php · 21 lines
php
<?php
declare(strict_types=1);

use PHPUnit\Framework\TestCase;

final class SignUpServiceTest extends TestCase
{
    public function testSendsWelcomeMailToNewUser(): void
    {
        $users = self::createStub(UserRepository::class);
        $users->method('existsByEmail')->willReturn(false);

        $mailer = $this->createMock(Mailer::class);
        $mailer->expects($this->once())
            ->method('send')
            ->with('[email protected]', 'Welcome!', $this->anything());

        $service = new SignUpService($users, $mailer);
        $service->register('[email protected]', 'correct horse');
    }
}

go deeper

for a junior

Know that createStub() gives canned answers and createMock() can also check calls, and which object in a small test is which.

for a middle

Explain the Stub and MockObject interfaces, when expectations are verified, and why PHPUnit 13 flags a mock that never gets one.

for a senior

Show you choose doubles by whether a call is the behaviour under test, and can clean a legacy suite full of createMock() used as stubs.

for a principal

Argue for a team convention on stubs versus mocks and how it keeps a large suite from breaking on harmless refactors.

## Two factories, two return types PHPUnit builds **test doubles** at runtime: it generates a class that extends the class you name, or implements the interface you name, and overrides every method it is allowed to override. What you get back depends on which factory on `TestCase` you call. | Factory | Returns | Configure answers | Configure expectations | |---|---|---|---| | `createStub(Foo::class)` | `Foo&Stub` | `method()->willReturn*()` | not available | | `createConfiguredStub(Foo::class, [...])` | `Foo&Stub` | from the array | not available | | `createMock(Foo::class)` | `Foo&MockObject` | `method()->willReturn*()` | `expects()->method()->with()` | | `createConfiguredMock(Foo::class, [...])` | `Foo&MockObject` | from the array | `expects()` afterwards | The difference is visible in the interfaces themselves. `PHPUnit\Framework\MockObject\Stub` declares a single method, `method()`. `MockObject` extends `Stub` and adds `expects(InvocationOrder $rule)`. In PHPUnit 13 `createStub()` is also a **static** method, while `createMock()` is an instance method; that fits the fact that only mocks are registered with the running test for later verification. ## What a stub can do A **stub** is a double that feeds the code under test with prepared answers. With a stub you: - name a method with `method('existsByEmail')`; - attach an answer: `willReturn()`, `willReturnMap()`, `willReturnCallback()`, `willReturnSelf()`, `willReturnArgument()`, `willReturnOnConsecutiveCalls()` or `willThrowException()`; - leave every other method alone, in which case PHPUnit generates a value from the declared return type (`false` for `bool`, `0` for `int`, `''` for `string`, `[]` for `array`, `null` for a nullable type, a fresh stub for an interface type). `createConfiguredStub()` is shorthand: it calls `createStub()` and then `method($name)->willReturn($value)` for each entry of the array you pass. A stub carries no count rule, so it never fails a test for being called too often or not at all. ## What a mock adds A **mock object** can do everything a stub does and can also carry **expectations**: 1. `expects()` takes an invocation-count rule from `TestCase`: `never()`, `once()`, `exactly(n)`, `atLeast(n)`, `atLeastOnce()` or `atMost(n)`. 2. `method()` names the method the rule applies to. 3. `with()` optionally constrains the arguments of each matching call. PHPUnit keeps every mock created by `createMock()` in a list on the test case. Wrong arguments and extra calls fail at the moment of the call; the remaining rules, such as a call that never happened, are verified after the test method returns. Each verified mock also adds to the test's assertion count, so a test whose only check is a mock expectation still counts as having asserted something. ## Choosing in the sign-up service test The reserved scenario for this leaf is a `SignUpService` that checks a `UserRepository` for an existing address and then sends a welcome mail through a `Mailer`. The repository only supplies data: whether the address exists. Nobody cares how often the service asks. That is a stub. The mailer call *is* the outcome: if `send()` is not called, or called with the wrong recipient, sign-up is broken. That is a mock. A practical rule of thumb: - **queries** that return data the code under test needs go to a stub; - **commands** whose effect you cannot observe any other way go to a mock; - if you find yourself writing `expects()` on a query, ask whether asserting on the service's return value would be clearer. ## The notice that enforces the split In PHPUnit 13, after a test finishes, any mock that has neither an invocation-count rule nor an argument rule produces a **PHPUnit notice**: "No expectations were configured for the mock object for ... Consider refactoring your test code to use a test stub instead." The fix is usually to change `createMock()` to `createStub()`. When a mock is created for a shared fixture and only some tests set expectations, the `#[AllowMockObjectsWithoutExpectations]` attribute on the test class or method switches the check off. `createConfiguredMock()` alone produces the same notice, because an array of return values is not an expectation. ## Pitfalls interviewers look for - Calling `expects()` on a double from `createStub()`: the generated class has no such method, so PHP throws an `Error` for an undefined method. - Believing the doubles run the real constructor: both factories skip it, so constructor side effects never happen. - Mocking every collaborator "to be thorough": each needless expectation ties the test to call counts that are not part of the behaviour. - Treating the notice as harmless noise: it points at a test that claims to verify interaction but verifies nothing. ## Doubles for more than one interface Sometimes a collaborator is typed as an intersection, for example a mailer that must also be `Countable`. PHPUnit 13 covers that case with a matching pair of factories: `createStubForIntersectionOfInterfaces([Mailer::class, Countable::class])` returns a stub, and `createMockForIntersectionOfInterfaces([...])` returns a mock that is registered for verification like any other. The same choice applies: pick the stub unless a call on the double is itself the outcome you are testing. Both factories accept interfaces only, so a class in the list is not an option.

  • What happens if you call expects() on a double made with createStub() in PHPUnit 13?
    The generated stub class only mixes in the stub API, so `expects()` does not exist on it and PHP throws an `Error` for an undefined method. The fix is to create that double with `createMock()`, or to drop the expectation if the call is not really part of the behaviour under test.
  • What does createConfiguredStub() save you, and is there a mock equivalent?
    `createConfiguredStub(Foo::class, ['isOpen' => true])` creates a stub and calls `method('isOpen')->willReturn(true)` for each array entry. `createConfiguredMock()` does the same on a mock, but the array only sets return values, so unless you add `expects()` afterwards PHPUnit 13 reports the same no-expectations notice.
  • Does a test whose only check is a mock expectation count as asserting something?
    Yes. When PHPUnit verifies a mock that has an invocation-count rule after the test method, it adds to the test's assertion count, so the test is not treated as one that performed no assertions.

saying these in an interview costs you the question

  • createStub and createMock are two names for the same double
  • You can add expects(once()) to a double returned by createStub()
  • The doubles call the real constructor of the class they replace
  • Mock every collaborator so the test checks all interactions
  • The no-expectations notice means the test has failed