Say your class depends on a service that hits a real payment gateway, sends real emails, or queries a real database. A unit test that exercises the real thing isn't a unit test anymore — it's slow, unreliable in CI, and might genuinely charge a credit card. A mock is a fake stand-in object that behaves exactly the way you tell it to, letting you test your class's logic completely in isolation from what it actually depends on.
What you'll learn in this lesson
- What
createMock()actually creates, and why it matters that it's built from an interface - How to "program" a mock's return value with
expects()/method()/willReturn() - How injecting a mock into the class under test isolates it from its real dependency
- When mocking is the right call, and when it's overkill
A real mock-based unit test, from the Examples module
namespace Drupal\Tests\phpunit_example\Unit;
use Drupal\phpunit_example\DisplayInfoInterface;
use Drupal\phpunit_example\DisplayManager;
use Drupal\Tests\UnitTestCase;
class DisplayManagerTest extends UnitTestCase {
public function testSimpleMockDisplayManager() {
// Get a mock object belonging to our desired interface.
$mock = $this->createMock(DisplayInfoInterface::class);
$this->assertTrue($mock instanceof DisplayInfoInterface);
// 'Program' our mock object to return a value for getDisplayName().
$mock->expects($this->any())
->method('getDisplayName')
->willReturn('the display name');
// Create a DisplayManager, the class we're actually testing here.
$dm = new DisplayManager();
$dm->addDisplayableItem($mock);
$this->assertEquals(1, $dm->countDisplayableItems());
$this->assertSame($mock, $dm->item('the display name'));
$this->assertNull($dm->item('nonexistent'));
}
}
Why the mock is built from an interface, not a concrete class
$this->createMock(DisplayInfoInterface::class) generates a fake object that satisfies DisplayInfoInterface — meaning it passes instanceof checks and type hints exactly like a real implementation would — but every one of its methods returns null until you explicitly program otherwise. Building from an interface (not a concrete class) is deliberate: it means DisplayManager can accept this mock in exactly the same place it would accept a real display item, with zero special-casing in the code under test.
Programming the mock's behavior
$mock->expects($this->any())
->method('getDisplayName')
->willReturn('the display name');
Read this as one sentence: "expect this method to be called any number of times; when it's called, return this value." $this->any() is an invocation-count matcher — swap it for $this->once() if the test should actually fail when the method isn't called exactly one time, which is a useful way to verify your code calls a dependency exactly as often as it should.
What's actually being tested here
Notice the assertions never touch the mock's internals directly — they check DisplayManager's own behavior (countDisplayableItems(), item()). The mock exists purely to give DisplayManager something that looks like a real DisplayInfoInterface object without needing a real one. This is the entire point of mocking: the test verifies DisplayManager's logic, completely independent of whatever a real display item implementation would actually do.
DisplayInfoInterface), never the class under test itself (DisplayManager) — a test that mocks its own subject verifies nothing real.See it for yourself
Run vendor/bin/phpunit --filter DisplayManagerTest on your DDEV/local environment. Try temporarily changing willReturn('the display name') to a different string and re-run it — watch assertSame($mock, $dm->item('the display name')) fail, since the mock no longer returns the name the test is looking it up by.
Quick check: would it make sense to mock a plain PHP value object with no side effects (say, a simple DateTime wrapper with no dependencies of its own)? Generally no — mocking exists to isolate your code from something expensive, unpredictable, or side-effecting (a database, an API call, a file write). A pure value object with deterministic behavior is usually cheap enough to just use directly in a test.
Key takeaways
$this->createMock(SomeInterface::class)creates a fake object satisfying that interface, with every method returningnulluntil programmed otherwise.expects()->method()->willReturn()is how you program a mock's behavior for a specific method call.- Always mock the dependencies of the class under test — never the class under test itself.
- Reach for mocking when a real dependency is slow, unreliable, or has side effects you don't want in a test run; skip it for cheap, deterministic values.
Coming up next
You've now seen both ends of Drupal's PHPUnit spectrum: a full functional test driving a real browser, and a fully isolated unit test with a mocked dependency. Next, we'll look specifically at writing focused unit tests for services — including PHPUnit's data provider pattern for testing many input combinations without duplicating test code.