← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Test Doubles

Testing PHP 8.0+ Intermediate
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints field states automated=no and the code_pattern is 'createMock() on third-party class or concrete class without interface'. No specialist tool in the tooling list catches over-mocking or wrong double type selection — it requires a human reviewer who understands the distinction between mocks, stubs, and fakes, and recognises when implementation details are being over-specified.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests a conceptual swap (createMock vs createStub), but the common_mistakes reveal deeper structural issues: mocking third-party libraries requires introducing adapter layers, and replacing over-mocked tests with behaviour-focused ones (or in-memory fakes) touches many test files and possibly production interfaces. This is more than a single-line fix across a codebase.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The applies_to scope is cli contexts with PHP 8.0+, but the tags (unit-testing, php) indicate this pattern affects every unit test written. A codebase that adopts over-mocking or incorrect double usage pays a persistent tax: tests become fragile on refactor, give false confidence, and slow down TDD cycles. It doesn't reshape the entire architecture but does impose ongoing drag across many work streams.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field explicitly states 'more mocks equals better tests' — a belief that seems correct (mocks = isolation = unit tests) but produces the opposite outcome: brittle tests that verify implementation details and break on refactor while giving false confidence. Developers coming from other frameworks or backgrounds naturally reach for mocks everywhere, making this a serious and pervasive cognitive trap.

About DEBT scoring →

Also Known As

mock stub spy fake dummy PHPUnit mock

TL;DR

Substitute objects used in tests to replace real dependencies — mocks, stubs, spies, fakes, and dummies each serve a different purpose.

Explanation

Stub: returns predefined values, no assertions. Mock: verifies interactions (was a method called, how many times, with what args). Spy: records calls for later assertion. Fake: a simplified but working implementation (e.g. in-memory database). Dummy: passed but never used, just to satisfy a parameter. PHPUnit provides mock objects via createMock() and createStub(). Mockery offers a more expressive DSL. Overusing mocks leads to tests that pass even when the real integration is broken.

Common Misconception

More mocks equals better tests. Over-mocking produces tests that verify implementation details rather than behaviour — they break on every refactor and give false confidence.

Why It Matters

Without test doubles, unit tests depend on real databases, APIs, and filesystems — making them slow, brittle, and environment-dependent. Doubles isolate the unit under test.

Common Mistakes

  • Mocking third-party libraries directly — wrap them in an adapter and mock the adapter instead.
  • Using mocks where a fake (in-memory implementation) would give more realistic test coverage.
  • Asserting on mock interactions instead of observable outputs — testing how rather than what.

Avoid When

  • Do not mock value objects or simple data structures — instantiate them directly.
  • Avoid mocking third-party libraries — wrap them in your own interface and mock that instead.

When To Use

  • Use stubs when you need a dependency to return a specific value without caring how it was called.
  • Use mocks when the interaction itself (method called, args, times) is the behaviour under test.

Code Examples

✗ Vulnerable
// Mocking a third-party class directly — fragile, couples to implementation
$guzzle = $this->createMock(\GuzzleHttp\Client::class);
$guzzle->method('request')->willReturn(new Response(200, [], 'ok'));
// Better: mock your own HttpClientInterface that wraps Guzzle
✓ Fixed
// Stub: provide a return value
$mailer = $this->createStub(MailerInterface::class);
$mailer->method('send')->willReturn(true);

// Mock: verify interaction
$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())
       ->method('send')
       ->with($this->equalTo('user@example.com'));

$service = new UserService($mailer);
$service->registerUser('user@example.com');

Added 31 Mar 2026
Views 64
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 1 ping W 0 pings T 2 pings F 3 pings S 3 pings S 1 ping M 1 ping T 3 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 0 pings S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Perplexity 12 Scrapy 10 Amazonbot 7 SEMrush 6 Google 5 Ahrefs 4 Unknown AI 3 Claude 2 Bing 2 ChatGPT 1 Meta AI 1 Sogou 1 Qwen 1
crawler 50 crawler_json 4 pre-tracking 1
DEV INTEL Tools & Severity
⚙ Fix effort: Medium
⚡ Quick Fix
Use createMock() for verifying interactions, createStub() for providing return values — never mock what you don't own
📦 Applies To
PHP 8.0+ cli phpunit phpspec
🔗 Prerequisites
🔍 Detection Hints
createMock() on third-party class or concrete class without interface
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File Tests: Regenerate


✓ schema.org compliant