Content · Web Development · PHP

Writing Testable Classes Without a Framework

Construct small classes whose dependencies arrive through interfaces or constructor parameters. Tests should check externally observable behavior, not private implementation details.

1 views Published Oct 17, 2026 Updated Oct 17, 2026 admin

Construct small classes whose dependencies arrive through interfaces or constructor parameters.

The examples focus on the operation itself, including the important distinction: Hard-coded service creation couples tests to network or database infrastructure.

What It Does


Use the examples below to observe the behavior on a small dataset before extending it to application code.

Before You Start


Read the input requirements before applying this pattern to production data.

Example 1: Basic Use


The fake dependency provides a deterministic test without requiring a test framework.

<?php
interface Clock { public function today(): string; }
class FakeClock implements Clock { public function today(): string { return "2026-01-01"; } }
class Badge { public function __construct(private Clock $clock) {}
    public function text(): string { return "Valid " . $this->clock->today(); }
}
echo (new Badge(new FakeClock()))->text();

Output from a successful local PHP 8.4 run:

Valid 2026-01-01

Example 2: Checking Behavior


The fake dependency provides a deterministic test without requiring a test framework. The injected fake supplies a different deterministic date for the test case.

<?php
interface Clock { public function today(): string; }
class FakeClock implements Clock { public function today(): string { return "2026-02-01"; } }
class Badge { public function __construct(private Clock $clock) {}
    public function text(): string { return "Valid " . $this->clock->today(); }
}
echo (new Badge(new FakeClock()))->text();

Output from a successful local PHP 8.4 run:

Valid 2026-02-01

Example 3: Practical Example


The fake dependency provides a deterministic test without requiring a test framework. The injected fake supplies a different deterministic date for the test case.

<?php
interface Clock { public function today(): string; }
class FakeClock implements Clock { public function today(): string { return "2026-03-01"; } }
class Badge { public function __construct(private Clock $clock) {}
    public function text(): string { return "Valid " . $this->clock->today(); }
}
echo (new Badge(new FakeClock()))->text();

Output from a successful local PHP 8.4 run:

Valid 2026-03-01

Important Behavior and Edge Cases


Tests should check externally observable behavior, not private implementation details.

Comparison with Related Approaches


ApproachDifference
Writing Testable Classes Without a FrameworkConstruct small classes whose dependencies arrive through interfaces or constructor parameters.
Related API or alternativeHard-coded service creation couples tests to network or database infrastructure.

Common Beginner Mistakes


MistakeCorrect approach
Ignoring the operation-specific limitsTests should check externally observable behavior, not private implementation details.
Confusing it with a related APIHard-coded service creation couples tests to network or database infrastructure.

Key Takeaways


  • Construct small classes whose dependencies arrive through interfaces or constructor parameters.
  • Tests should check externally observable behavior, not private implementation details.
  • Hard-coded service creation couples tests to network or database infrastructure.

Use Writing Testable Classes Without a Framework when its documented behavior matches the problem. Test the relevant edge cases in your own runtime before deploying it.

Share this resourceSend this page to someone who may find it useful.