Gerard Meszaros introduces the concept of test doubles in his “xUnit Test Patterns” book like so:
Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. This could be because they aren’t available, they will not return the results needed for the test or because executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behavior of the SUT.
When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn’t have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one!
PHPUnit provides a powerful and flexible API for creating and configuring test stubs and mock objects. These test doubles are essential for unit testing, as they allow us to isolate the code under test from its dependencies and verify its behaviour without executing the code of the real collaborating objects.
A test stub replaces a real dependency and can be configured to return predefined values or throw exceptions. Test stubs give us control over indirect inputs to our system under test, enabling us to force it onto specific execution paths and test different scenarios without relying on external systems or services.
A mock object is a type of test stub that can be configured with expectations about how it will be called. Mock objects serve as observation points, enabling us to verify indirect outputs and communication between our system under test and its collaborators. By expecting that specific methods were called with the expected arguments, we can ensure that our code interacts correctly with its dependencies.
This chapter focuses exclusively on test stubs and mock objects. Other types of test double, such as dummies, fakes, and spies, are beyond the scope of this documentation.
Use a test stub when:
You need to control what a dependency returns
You are testing the logic of the SUT
The interactions with the dependency do not matter
You need to isolate the SUT from slow or unavailable dependencies
Use a mock object when:
You need to verify that methods are called
You are testing the communication between objects
The number of method calls matters
The arguments passed to methods matter
For a detailed discussion of the conceptual differences between test stubs and mock objects and when to use which, see “Testing with(out) dependencies”.
Example Code
Some of the examples in this chapter use an interface named Database and a class named Service. These are introduced now so that we are familiar with them when they appear in the examples.
<?php declare(strict_types=1);
interface Database
{
/**
* @throws DatabaseException
*/
public function execute(string $sql, float|int|string ...$parameters): true;
/**
* @throws DatabaseException
*/
public function query(string $sql, float|int|string ...$parameters): array;
}
Explanation:
This interface defines a contract for database operations
execute() is used for write operations (INSERT, UPDATE, DELETE), returns true on success, and throws an exception of failure
query() is used for read operations (SELECT), returns an array of results on success, and throws an exception of failure
Both methods accept SQL strings and variadic arguments for prepared statements
<?php declare(strict_types=1);
final readonly class Service
{
private Database $database;
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* @throws ServiceException
*/
public function doSomething(): bool
{
try {
$rows = $this->database->query(
'SELECT foo FROM bar WHERE baz = ?;',
'value',
);
} catch (DatabaseException) {
throw new ServiceException;
}
if ($rows !== []) {
// ...
return true;
}
return false;
}
/**
* @throws ServiceException
*/
public function doSomethingElse(): void
{
// ...
$this->database->execute(
'INSERT INTO bar (foo, baz) VALUES (?, ?);',
'value',
'another value',
);
}
}
Explanation:
Service is a final readonly class that depends on Database
The Database dependency is injected through the constructor, which makes the class testable in isolation from both the dependency and the database server
doSomething() queries the database and returns true if rows are found, false otherwise
doSomethingElse() executes an INSERT statement to add data to the database
A test stub provides a replacement for a real collaborating object (a dependency) that your code interacts with. Test stubs allow you to test code in isolation without executing the actual implementation of the dependency.
A test stub is a replacement for a real component on which the System Under Test (SUT) depends. This gives the test a control point for the indirect inputs of the SUT. By controlling these indirect inputs, you can force the SUT into specific execution paths that you want to verify in your test.
Use test stubs when you want to:
Decouple your code from slow or unavailable dependencies (databases, external APIs)
Provide specific return values to test different code paths
Test error handling by simulating failures
Focus on testing the logic of the SUT rather than its dependencies
If your code depends on a service that returns data, you can use a test stub to control the results of that service without actually calling it.
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ServiceTest extends TestCase
{
public function testDoSomethingReturnsTrueWhenQueryReturnsRows(): void
{
$database = $this->createStub(Database::class);
$database
->method('query')
->willReturn([['foo' => 'bar']]);
$service = new Service($database);
$this->assertTrue($service->doSomething());
}
}
1. Creating the test stub
$database = $this->createStub(Database::class);
What: Creates a test stub that implements the Database interface
How: createStub() generates a test stub where all methods return default values unless configured otherwise
Why: We need an object that “looks like” Database to test Service in isolation from a real database connection
Test stubs are ideal when we only need to control what the dependency returns (indirect input).
2. Configuring the test stub
$database
->method('query')
->willReturn([['foo' => 'bar']]);
What: Configures the test stub to return a specific value when query() is called
How:
method('query') specifies which method to configure
willReturn([['foo' => 'bar']]) sets the return value to an array containing one row
Why: This simulates the scenario where the database query finds matching rows
3. Creating the system under test
$service = new Service($database);
What: Instantiates the Service class with the test stub as its dependency
How: The test stub is passed to the constructor, satisfying the Database type requirement
Why: This is the object we are actually testing and by injecting the test stub, we control the database behavior
4. Asserting the expected behavior
$this->assertTrue($service->doSomething());
What: Verifies that doSomething() returns true
How: assertTrue() fails the test if the value is not exactly true
Why: When the query returns rows, doSomething() should return true
Key Concept: Indirect Input
Indirect input occurs when the system under test receives data from a dependency rather than directly from arguments passed to the tested method or function. In this test:
The test cannot directly pass data to doSomething() as it takes no parameters
Instead, doSomething() gets its data by calling $this->database->query()
The test stub provides this indirect input by returning [['foo' => 'bar']]
You can configure test stubs to throw exceptions, enabling you to test how your code handles errors.
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ServiceTest extends TestCase
{
public function testExceptionIsThrownWhenSomethingGoesWrong(): void
{
$database = $this->createStub(Database::class);
$database
->method('query')
->willThrowException(new DatabaseException);
$service = new Service($database);
$this->expectException(ServiceException::class);
$service->doSomething();
}
}
1. Creating the test stub
$database = $this->createStub(Database::class);
What: Creates a test stub that implements the Database interface
How: createStub() generates a test stub where all methods return default values unless configured otherwise
Why: We need an object that “looks like” Database to test Service in isolation from a real database connection
Test stubs are ideal when we only need to control what the dependency returns (indirect input).
2. Configuring the test stub
$database
->method('query')
->willThrowException(new DatabaseException);
What: Configures the test stub to throw a specific exception when query() is called
How:
method('query') specifies which method to configure
willThrowException(new DatabaseException) sets the exception to be thrown
Why: This simulates the scenario where an error occurs while querying the database
3. Configuring the expectation
$this->expectException(ServiceException::class);
What: Configures the test to only be successful if a ServiceException is thrown
How: We pass the name of the exception we expect to expectException()
Why: We want to test that a DatabaseException thrown by a Database implementation results in a ServiceException being thrown
4. Creating the system under test
$service = new Service($database);
What: Instantiates the Service class with the test stub as its dependency
How: The test stub is passed to the constructor, satisfying the Database type requirement
Why: This is the object we are actually testing and by injecting the test stub, we control the database behavior
4. Invoking what we want to test
$service->doSomething();
createStub()
Creates a test stub for the specified interface (or extendable class).
$stub = $this->createStub(InterfaceName::class);
All methods of the original type are replaced with an implementation that returns an automatically generated value that satisfies the method’s return type declaration without calling the original method. These methods are referred to as “doubled methods” or “stubbed methods”.
Doubled methods can be configured using the methods described below.
Limitation: final classes
Please note that final classes cannot be doubled.
Limitation: final, private, and static methods
Please note that final, private, and static methods cannot be doubled. They are ignored by PHPUnit’s test double functionality and retain their original behavior except for static methods which will be replaced by a method throwing an exception.
Limitation: Enumerations
Enumerations (enum) are final classes and therefore cannot be doubled.
createStubForIntersectionOfInterfaces()
Creates a test stub for an intersection of interfaces.
$stub = $this->createStubForIntersectionOfInterfaces(
[InterfaceA::class, InterfaceB::class]
);
This is useful when you need to replace an object that implements multiple interfaces.
createConfiguredStub()
Creates a test stub with methods already configured to return specific values.
$stub = $this->createConfiguredStub(
InterfaceName::class,
[
'methodOne' => 'return value one',
'methodTwo' => 'return value two',
]
);
// $stub->methodOne() will return "return value one"
// $stub->methodTwo() will return "return value two"
This is a convenience method for simple cases.
getStubBuilder()
The getStubBuilder() method provides a fluent API for creating test stubs. It should only be used for edge cases that are not supported by the simpler createStub() or createStubForIntersectionOfInterfaces() methods.
Use getStubBuilder() only when you need advanced configuration such as:
Specifying a custom class name for the test stub
Enabling the original constructor with custom arguments
Creating partial test stubs (only doubling specific methods)
Controlling clone behavior
Disabling automatic return value generation
The getStubBuilder(string $type) method returns an object that can be used to configure and subsequently perform the creation of a test stub for the specified interface (or extendable class).
The object returned by getStubBuilder() has, among other methods, a method named getStub(). This creates and returns the configured test stub. This method must be called last in the fluent API’s method call chain.
The following methods can be used on the object returned by getStubBuilder() to configure the creation of the test stub:
setStubClassName(string $name)
Specifies a custom class name for the generated test stub class.
Note
The specified class name must not already exist.
onlyMethods(array $methods)
Specifies which methods should be doubled (stubbed). Methods not in this list will retain their original implementation, creating a partial double.
Note
All specified methods must exist in the class.
setConstructorArgs(array $arguments)
Specifies the arguments to pass to the constructor when enableOriginalConstructor() (see below) is used.
disableOriginalConstructor()
This disables the invocation of the original constructor. This is useful when you want to create a partial double and the constructor either has side effects or requires dependencies that you do not want to provide.
Note
createStub(), createConfiguredStub(), createMock(), and createConfiguredMock() create test doubles without invoking the original constructor when they are used to create a test double for an extendable class.
enableOriginalConstructor()
Enables the invocation of the original constructor. Use this with setConstructorArgs() (see above) to pass required arguments.
Note
This is the default behaviour. The enableOriginalConstructor() method only exists in case you want to explicitly indicate in your test code that you are relying on this behaviour.
disableOriginalClone()
Disables the invocation of the original __clone() method when the test stub is cloned.
Note
The original __clone() method is not called for test doubles for extendable classes created by createStub(), createConfiguredStub(), createMock(), and createConfiguredMock().
enableOriginalClone()
Enables the invocation of the original __clone() method when the test stub is cloned.
Note
This is the default behaviour. The enableOriginalClone() method only exists in case you want to explicitly indicate in your test code that you are relying on this behaviour.
enableAutoReturnValueGeneration()
Enables automatic generation of return values for doubled methods that do not have explicit return value configuration.
Note
This is the default behaviour. The enableAutoReturnValueGeneration() method only exists in case you want to explicitly indicate in your test code that you are relying on this behaviour.
disableAutoReturnValueGeneration()
Disables automatic generation of return values. When disabled, stubbed methods without explicit configuration will return null or throw an exception depending on the declared return type.
willReturn()
Configures a method to return a specific value.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturn('result');
// $stub->doSomething() always returns "result"
The return value must be compatible with the method’s return type declaration.
willReturn() can be used to configure a method to return different values on consecutive calls:
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturn('first result', 'second result');
// $stub->doSomething() returns "first result" when it is invoked for the first time
// $stub->doSomething() returns "second result" when it is invoked for the second time
// $stub->doSomething() will trigger an error when it is invoked more than twice
If a method is configured to return different values on consecutive calls, it can only be invoked a number of times equivalent to the number of configured return values.
willReturnSelf()
Configures a method to return the test stub object itself.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturnSelf();
This is useful for testing fluent interfaces.
willReturnArgument()
Configures a method to return one of its arguments.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturnArgument(0);
// $stub->doSomething('some value') returns 'some value'
The argument index is zero-based.
willReturnMap()
Configures a method to return different values based on the arguments it receives.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturnMap([
['foo', 'bar', 'baz'],
['one', 'two', 'three'],
]);
// $stub->doSomething('foo', 'bar') returns 'baz'
// $stub->doSomething('one', 'two') returns 'three'
Each inner array contains the method arguments followed by the return value.
willReturnCallback()
Configures a method to return the result of a callback function.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willReturnCallback(
static fn(string $input) => strtoupper($input)
);
// $stub->doSomething('string') returns 'STRING'
The callback receives the method arguments and can implement complex logic.
PHP 8.4 introduced the language feature of get-hooked properties.
The example below shows an interface that declares a get-hooked property:
<?php declare(strict_types=1);
interface InterfaceWithGetHookedProperty
{
public string $property { get; }
}
The behaviour of the get-hooked property property can be configured like so:
<?php declare(strict_types=1);
use PHPUnit\Framework\MockObject\Runtime\PropertyHook;
use PHPUnit\Framework\TestCase;
final class ExampleTest extends TestCase
{
public function testExample(): void
{
$stub = $this->createStub(
InterfaceWithHookedProperty::class,
);
$stub
->method(PropertyHook::get('property'))
->willReturn('value');
$this->assertSame('value', $stub->property);
}
}
In the example shown above, PropertyHook::get('property') to specify that we want to configure the behaviour of the method that is called when the property named property is accessed for reading.
willThrowException()
Configures a method to throw an exception instead of returning a value.
$stub = $this->createStub(InterfaceName::class);
$stub
->method('doSomething')
->willThrowException(new Exception);
// $stub->doSomething() throws the configured exception
Please note that methods with no configured behaviour will automatically and recursively stub return values based on the return type of the method. Consider the example shown below:
<?php declare(strict_types=1);
class C
{
public function m(): D
{
// Do something.
}
}
In the above example, the C::m() method has a return type declaration indicating that it returns an object of type D. When a test double for C is created and no return value is configured for m() using willReturn(), for example, PHPUnit will automatically create a test double for D to be returned when m() is invoked.
Similarly, if m() had a return type declaration for a scalar type, a return value such as 0 (for int), 0.0 (for float), "" (for string), etc. would be generated.
You can disable this return value generation using the #[DisableReturnValueGenerationForTestDoubles] attribute on the test case class.
A mock object is a test stub that can additionally be configured with expectations about how it should be called. Mock objects allow you to verify the communication between your System Under Test and its collaborators.
While a test stub provides control over indirect inputs (data flowing into the SUT), a mock object provides an observation point for indirect outputs (method calls from the SUT to its dependencies). Mock objects verify that the SUT communicates correctly with its dependencies.
Use mock objects when you want to:
Verify that specific methods are called
Verify the arguments passed to methods
Verify the number of times methods are called
Test the coordination and communication between objects
If you create a mock object but do not configure any expectations on it, PHPUnit will emit a notice. This indicates you should either add expectations or use createStub() instead.
Please read “Testing with(out) dependencies” and “The Stub/Mock Intervention” for some background on why this distinction between test stubs and mock objects is critical.
Use a mock object to verify that actions occur when your code triggers them in its dependencies.
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ServiceTest extends TestCase
{
public function testDoSomethingElseExecutesInsertQuery(): void
{
$database = $this->createMock(Database::class);
$database
->expects($this->once())
->method('execute')
->with(
'INSERT INTO bar (foo, baz) VALUES (?, ?);',
'value',
'another value',
);
$service = new Service($database);
$service->doSomethingElse();
}
}
1. Creating the mock object
$database = $this->createMock(Database::class);
What: Creates a mock object that implements the Database interface
How: createMock() generates a mock object that can verify method calls and their arguments
Why: We need to verify that Service correctly calls the database’s execute() method
Mock objects are ideal when we need to verify how the system under test interacts with its dependencies (indirect output).
2. Setting up expectations
$database
->expects($this->once())
->method('execute')
->with(
'INSERT INTO bar (foo, baz) VALUES (?, ?);',
'value',
'another value',
);
What: Configures the mock object to expect a specific method call with specific arguments
How:
expects($this->once()) - The method must be called exactly once; the test fails if called zero times or more than once
method('execute') - Specifies which method should be called
with(…) - Specifies the arguments that must be passed; the test fails if different arguments are used
Why: This is the core of mock-based testing. We verify that doSomethingElse() sends the correct SQL and parameters to the Database object
3. Creating the system under test
$service = new Service($database);
What: Instantiates the Service class with the mock object as its dependency
How: The mock object is passed to the constructor, satisfying the Database type requirement
Why: This is the object we are actually testing and by injecting the mock object, we can verify the communication between Service and Database
4. Executing the system under test
$service->doSomethingElse();
What: Calls the method we want to test
When the test method completes, PHPUnit automatically verifies that all expectations set on the mock object were met.
Key Concept: Indirect Output
Indirect output occurs when the system under test sends data to a dependency rather than returning it directly. In this test:
doSomethingElse() returns nothing (void), there is no direct output to assert
Instead, the method’s effect is calling $this->database->execute() with specific arguments
The mock object captures this indirect output and verifies it matches our expectations
createMock()
Creates a mock object for the specified interface (or extendable class).
$mock = $this->createMock(InterfaceName::class);
All methods can be configured with return values and expectations.
Limitation: final classes
Please note that final classes cannot be doubled.
Limitation: final, private, and static methods
Please note that final, private, and static methods cannot be doubled. They are ignored by PHPUnit’s test double functionality and retain their original behavior except for static methods which will be replaced by a method throwing an exception.
Limitation: Enumerations
Enumerations (enum) are final classes and therefore cannot be doubled.
createMockForIntersectionOfInterfaces()
Creates a mock object for an intersection of interfaces.
$mock = $this->createMockForIntersectionOfInterfaces(
[InterfaceA::class, InterfaceB::class]
);
This is useful when you need to replace an object that implements multiple interfaces.
createConfiguredMock()
Creates a mock object with methods already configured to return specific values.
$mock = $this->createConfiguredMock(
InterfaceName::class,
[
'methodOne' => 'return value one',
'methodTwo' => 'return value two',
]
);
// $mock->methodOne() will return "return value one"
// $mock->methodTwo() will return "return value two"
This is a convenience method for simple cases.
getMockBuilder()
The getMockBuilder() method provides a fluent API for creating mock objects. It should only be used for edge cases that are not supported by the simpler createMock() or createMockForIntersectionOfInterfaces() methods.
The documentation and recommendations for getStubBuilder() (see above) also apply to getMockBuilder(), with two differences:
The method for specifying a custom class name for the generated mock object class is setMockClassName()
The name of the method that must be called last in the fluent API’s method call chain is getMock()
Mock objects support all the same methods for configuring behavior (return values, exceptions) as test stubs. See the test stub reference section above for detailed examples of these methods.
Expectations define how many times and with what arguments a method should be called.
once()
The method must be called exactly once.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->once())
->method('doSomething');
once() is a convenience wrapper for exactly(1).
exactly(int $count)
The method must be called exactly $count times.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->exactly(2))
->method('doSomething');
atLeastOnce()
The method must be called at least once.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->atLeastOnce())
->method('doSomething');
atLeastOnce() is a convenience wrapper for atLeast(1).
Avoid using atLeastOnce()
Non-exact invocation expectations like atLeastOnce() should generally be avoided because they make test intent less explicit. When the exact number of invocations does not matter, a test stub is usually more appropriate than a mock object. When the number of invocations does matter, exactly() or once() communicates intent more clearly.
See GitHub issue #6483 for details.
atLeast(int $requiredInvocations)
The method must be called at least $requiredInvocations times.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->atLeast(2))
->method('doSomething');
Avoid using atLeast()
Non-exact invocation expectations like atLeast() should generally be avoided because they make test intent less explicit. When the exact number of invocations does not matter, a test stub is usually more appropriate than a mock object. When the number of invocations does matter, exactly() communicates intent more clearly.
See GitHub issue #6483 for details.
atMost(int $allowedInvocations)
The method must not be called more than $allowedInvocations times.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->atMost(2))
->method('doSomething');
Avoid using atMost()
Non-exact invocation expectations like atMost() should generally be avoided because they make test intent less explicit. When the exact number of invocations does not matter, a test stub is usually more appropriate than a mock object. When the number of invocations does matter, exactly() or never() communicates intent more clearly.
See GitHub issue #6483 for details.
never()
The method must not be called.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->never())
->method('doSomething');
never() is a convenience wrapper for exactly(0).
Deprecation: any() is deprecated
The any() matcher, used as $this->expects($this->any()), is deprecated. It will be removed in PHPUnit 14.
Using any() with expects() is contradictory: you are creating a mock object (designed to verify communication between objects) while essentially saying “I don’t care if this communication happens at all”.
If you do not need to verify that a method is called, use createStub() instead of createMock(). Test stubs are the appropriate choice when you only need to control what a dependency returns without verifying how many times it is called.
See GitHub issue #6461 for details.
with()
The with() method verifies the arguments passed to the mocked method.
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->once())
->method('doSomething')
->with('argument 1', 'argument 2');
In the example shown above, we configure the mock object to expect exactly one call to the method doSomething(). For this single call, the arguments "argument 1" and argument 2" must be passed.
The example shown above is equivalent to the following:
$mock = $this->createMock(InterfaceName::class);
$mock
->expects($this->once())
->method('doSomething')
->with($this->equalTo('argument 1'), $this->equalTo('argument 2'));
The with() method verifies the arguments passed to the mocked method using Constraint objects. If a value passed to with() is not a Constraint object then that value is automatically wrapped in a Constraint object that verifies equality. A Constraint object that verifies equality can be manually created using $this->equalTo().
Deprecation: Using with() on test stubs has no effect
Calling with() on a test stub (created using createStub(), for instance) has no effect because argument verification is only performed for mock objects.
Since PHPUnit 12.5.11, using with() on a test stub is deprecated and will trigger a deprecation warning. Using with() on a test stub no longer works in PHPUnit 13. Use createMock() instead of createStub() when you need to verify the arguments passed to a method.
PHPUnit provides many constraint methods for argument verification:
Identity and Equality
identicalTo()
equalTo()
equalToCanonicalizing()
equalToIgnoringCase()
equalToWithDelta()
objectEquals()
Cardinality
isEmpty()
countOf()
greaterThan()
greaterThanOrEqual()
lessThan()
lessThanOrEqual()
Math
isFinite()
isInfinite()
isNan()
Boolean
isFalse()
isTrue()
Operator
logicalAnd()
logicalNot()
logicalOr()
logicalXor()
String
isJson()
matches()
matchesRegularExpression()
stringContains()
stringEndsWith()
stringEqualsStringIgnoringLineEndings()
stringStartsWith()
Traversable
arrayHasKey()
containsEqual()
containsIdentical()
containsOnlyArray()
containsOnlyBool()
containsOnlyCallable()
containsOnlyClosedResource()
containsOnlyFloat()
containsOnlyInstancesOf()
containsOnlyInt()
containsOnlyIterable()
containsOnlyNull()
containsOnlyNumeric()
containsOnlyObject()
containsOnlyResource()
containsOnlyScalar()
containsOnlyString()
isList()
Type
isArray()
isBool()
isCallable()
isClosedResource()
isFloat()
isInstanceOf()
isInt()
isIterable()
isNull()
isNumeric()
isObject()
isResource()
isScalar()
isString()
Filesystem
directoryExists()
fileExists()
isReadable()
isWritable()
The id() and after() methods allow us to define dependencies between mock expectations, ensuring that one method is called only after another method has been called first.
id(string $id): Assigns a unique identifier to an expectation
after(string $id): Specifies that this expectation should only be verified after the expectation with the given ID has been matched
Below is a complete example demonstrating how to use id() and after() to verify that two() is called after one():
public function testTwoIsCalledAfterOne(): void
{
$mock = $this->createMock(ServiceInterface::class);
$mock
->expects($this->once())
->method('one')
->id('first-call');
$mock
->expects($this->once())
->method('two')
->after('first-call');
$mock->one();
$mock->two();
}
1. Configuring the first expectation
$mock
->expects($this->once())
->method('one')
->id('first-call');
expects($this->once()) sets up an expectation that the following method should be called exactly once
method('one') specifies that this expectation applies to the one() method
id('first-call') assigns the unique identifier "first-call" to this expectation. This ID can be referenced by other expectations using after().
2. Configuring the second expectation
$mock
->expects($this->once())
->method('two')
->after('first-call');
expects($this->once()) sets up an expectation that two() should be called exactly once
method('two') specifies that this expectation applies to the two() method
after('first-call') creates a dependency on the expectation with ID "first-call"'"
This means:
The expectation for two() will only be verified after one() has been called
If two() is called before one(), that call will not count toward satisfying this expectation
The test will fail if two() is not called after one() has been called
3. Executing the system under test
$mock->one();
$mock->two();
The call to one() on the mock object satisfies the first expectation and “unlocks” the second expectation
The call to two() on the mock object satisfies the second expectation, since one() was already called
If two() is called before one(), that call does not count toward satisfying the after() expectation.
Attempting to register the same ID twice will cause the test to error. Using after() with an ID that has not been registered with id() will cause the test to fail.
Warning: Avoid using id() and after()
The id() and after() methods should be avoided as they introduce unnecessary complexity and make tests harder to understand and maintain.
Tests that rely on strict call ordering are often brittle and may break when implementation details change, even if the behavior remains correct.
Why using id() and after() should be avoided:
Brittle Tests: Tests that verify call order are tightly coupled to implementation details. Refactoring code that changes the order of internal calls (without changing behavior) will break these tests.
Complexity: The id() and after() mechanism adds cognitive complexity. Readers of your tests need to understand this additional concept.
Hidden Dependencies: The relationship between expectations is not immediately obvious, making tests harder to debug when they fail.
Limited Flexibility: The mechanism only supports simple “A before B” relationships. More complex ordering requirements become even more unwieldy.
Test the final state or output rather than intermediate calls
Use integration tests for workflows where order matters
Design interfaces that do not require specific call ordering
The never return type indicates that a function or method will never return normally. Such methods either:
Throw an exception
Call exit() or die()
Enter an infinite loop
When writing unit tests, mocking methods with a never return type requires special handling because the mock cannot actually “never return” as it must do something when called.
When you create a mock object of an interface or extendable class that has a method with a never return type, the mock object will throw a NeverReturningMethodException when that method is called. This exception simulates the behavior of a method that never returns normally.
Consider the following scenario where we have an ErrorHandler interface with a handle() method that has a never return type:
<?php declare(strict_types=1);
interface ErrorHandler
{
public function handle(Exception $e): never;
}
<?php declare(strict_types=1);
final readonly class ErrorHandlerImplementation implements ErrorHandler
{
public function handle(Exception $e): never
{
print $e->getMessage();
exit;
}
}
<?php declare(strict_types=1);
final readonly class ServiceImplementation implements Service
{
private ErrorHandler $errorHandler;
public function __construct(ErrorHandler $errorHandler)
{
$this->errorHandler = $errorHandler;
}
public function doSomething(): void
{
try {
// ...
throw new Exception('message');
} catch (Exception $e) {
$this->errorHandler->handle($e);
}
}
}
We want to test that, when an exception is thrown during the execution of ServiceImplementation::doSomething(), the ErrorHandler::handle() method is called with an exception object of the expected type. Here is how to do that:
1. Creating the mock object
$errorHandler = $this->createMock(ErrorHandler::class);
This line creates a mock object that implements the ErrorHandler interface. The mock object will have all the methods defined in the interface, including the handle() method with its never return type.
2. Configuring the mock object
$errorHandler
->expects($this->once())
->method('handle')
->with($this->isInstanceOf(Exception::class));
This fluent chain configures the mock object’s behavior and expectations:
->expects($this->once()): Sets up an expectation that the handle() method will be called exactly once during the test. If the method is not called, or called more than once, the test will fail.
->method('handle'): Specifies that we are configuring the handle() method of the mock object.
->with($this->isInstanceOf(Exception::class)): Specifies that the handle() method must be called with an argument that is an instance of the Exception class. This constraint validates the argument passed to the method.
3. Creating the system under test
$service = new ServiceImplementation($errorHandler);
This line creates an instance of the ServiceImplementation class, injecting the mock object we created and configured to replace the ErrorHandler dependency. This is standard dependency injection, allowing us to test ServiceImplementation in isolation from its dependency ErrorHandler.
4. Configuring the expectation that the mocked method never returns
$this->expectException(NeverReturningMethodException::class);
This line tells PHPUnit to expect that a NeverReturningMethodException will be thrown during the test. This is the key to testing methods with never return type:
When $service->doSomething() is called, it will internally call $this->errorHandler->handle($e)
Since handle() has a never return type, PHPUnit throws a NeverReturningMethodException
By expecting this exception, we document our assumption that the handle() method never returns
5. Executing the system under test
$service->doSomething();
This line calls the method we want to test. The execution flow is:
doSomething() is called on the service
Inside doSomething(), an exception is thrown and caught
The caught exception is passed to $this->errorHandler->handle($e)
The mock object’s handle() method is invoked
PHPUnit verifies the expectation (called once with an Exception instance)
PHPUnit throws NeverReturningMethodException because the method has a never return type
The test passes because we expected this exception
Always use $this->expectException(NeverReturningMethodException::class) when testing code that calls a mocked method with never return type.
The exception replaces the “never return” behavior: In real code, a method with never return type would call exit(), throw an exception, or loop forever. In tests, NeverReturningMethodException simulates this by providing a controlled way to exit the method.
Verification still works: Even though an exception is thrown, PHPUnit still verifies that your expectations (like expects($this->once()) and with()) are met.
This pattern allows you to effectively test code that depends on methods that never return normally, while still being able to verify that those methods are called with the correct arguments.
The example below shows an interface that declares a set-hooked property:
<?php declare(strict_types=1);
interface InterfaceWithSetHookedProperty
{
public string $property { set; }
}
Expectations for the set-hooked property property can be configured like so:
<?php declare(strict_types=1);
use PHPUnit\Framework\MockObject\Runtime\PropertyHook;
use PHPUnit\Framework\TestCase;
final class SetHookedPropertyMockExampleTest extends TestCase
{
public function testExample(): void
{
$mock = $this->createMock(
InterfaceWithSetHookedProperty::class,
);
$mock
->expects($this->once())
->method(PropertyHook::set('property'))
->with('value');
$mock->property = 'value';
}
}
In the example shown above, PropertyHook::set('property') to specify that we want to configure an expectation for the method that is called when the property named property is accessed for writing.
Favour doubling interfaces over doubling classes.
Use meaningful names: Name your test doubles clearly to indicate their purpose.
Keep it simple: Do not over-configure test doubles. Only configure the methods you need.
Test at the right level: Mock service boundaries (repositories, external services), not domain objects or value objects.
One concept per test: Test either state (with stubs) or behavior (with mocks), not both in the same test.
Avoid brittle tests: Do not mock internal implementation details. Mock interfaces and public contracts.
Review PHPUnit notices: If you see a notice about unused mock objects, consider whether you should add expectations or use a test stub instead.
© 2005–2025 Sebastian Bergmann
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://docs.phpunit.de/en/12.5/test-doubles.html