Test doubles: Difference between revisions
No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
A '''[[wp:Test double]]''' is any object that stands in for a real dependency in automated testing. Usually in [[PHPUnit]], we make test doubles for other classes, but you can also double PHP native functions or <abbr title="Closures are small use-once functions which are useful as parameters for callback functions like array_walk, array_map, etc. Using closure functions in PHP can help to make code more succinct, expressive, and safer. Closures can access private data of an object.">closures</abbr>. | A '''[[wp:Test double]]''' is any object that stands in for a real dependency in automated testing. Usually in [[PHPUnit]], we make test doubles for other classes, but you can also double PHP native functions or <abbr title="Closures are small use-once functions which are useful as parameters for callback functions like array_walk, array_map, etc. Using closure functions in PHP can help to make code more succinct, expressive, and safer. Closures can access private data of an object.">closures</abbr><ref>aka anonymous functions<br>https://www.php.net/manual/en/functions.anonymous.php<br>https://www.php.net/manual/en/class.closure.php</ref>. | ||
There are 5 types of Test doubles | There are 5 types of Test doubles |
Revision as of 16:09, 14 February 2025
A wp:Test double is any object that stands in for a real dependency in automated testing. Usually in PHPUnit, we make test doubles for other classes, but you can also double PHP native functions or closures[1].
There are 5 types of Test doubles
- Dummy
- "A dummy argument" - used as a placeholder when an argument needs to be passed in.
- Stub
- Provides fake or 'canned' data to the System Under Test (SUT).
- Spy
- Records information about how it is used, and can provide that information back to the test.
- Mock
- Defines an expectation on how it will be used, and with what parameters. Will cause a test to fail automatically if the expectation isn’t met.
- Fake
- "Looks real, but it's not." An actual (simplified) implementation of the contract, unsuitable for production.
Dummy[edit]
class User {
public function __construct(private Logger $logger) {}
}
class Logger {}
class UserTest extends \PHPUnit\Framework\TestCase {
public function testUser() {
$dummyLogger = $this->createMock(Logger::class);
$user = new User($dummyLogger);
$this->assertInstanceOf(User::class, $user);
}
}
Stub[edit]
class User {
public function __construct(private Logger $logger) {}
public function logMessage($message) {
return $this->logger->log($message);
}
}
Spy[edit]
class User {
public function __construct(private Logger $logger) {}
public function logMessage($message) {
$this->logger->log($message);
}
}
class Logger {
public function log($message) {
// Actual logging logic
}
}
class UserTest extends \PHPUnit\Framework\TestCase {
public function testLogMessage() {
$spyLogger = $this->createMock(Logger::class);
$spyLogger->expects($this->once())
->method('log')
->with($this->equalTo('Test message'));
$user = new User($spyLogger);
$user->logMessage('Test message');
}
}
class Logger {
public function log($message) {
// Actual logging logic
}
}
class UserTest extends \PHPUnit\Framework\TestCase {
public function testLogMessage() {
$stubLogger = $this->createStub(Logger::class);
$stubLogger->method('log')->willReturn(true);
$user = new User($stubLogger);
$this->assertTrue($user->logMessage('Test message'));
}
}
Mock[edit]
class User {
public function __construct(private Logger $logger) {}
public function logMessage($message) {
$this->logger->log($message);
}
}
class Logger {
public function log($message) {
// Actual logging logic
}
}
class UserTest extends \PHPUnit\Framework\TestCase {
public function testLogMessage() {
$mockLogger = $this->createMock(Logger::class);
$mockLogger->expects($this->once())
->method('log')
->with($this->equalTo('Test message'));
$user = new User($mockLogger);
$user->logMessage('Test message');
}
}
Good reads: Mocks Aren't Stubs
- Martin Fowler
02 January 2007
The term 'Mock Objects' has become a popular one to describe special case objects that mimic real objects for testing. Most language environments now have frameworks that make it easy to create mock objects. What's often not realized, however, is that mock objects are but one form of special case test object, one that enables a different style of testing. In this article I'll explain how mock objects work, how they encourage testing based on behavior verification, and how the community around them uses them to develop a different style of testing.
Fake[edit]
class UserRepository {
private $db;
public function __construct(Database $db) {
$this->db = $db;
}
public function findUserById($id) {
return $this->db->query("SELECT * FROM users WHERE id = ?", [$id]);
}
}
class FakeDatabase {
private $data = [];
public function query($query, $params) {
// Simplified query logic for testing
foreach ($this->data as $row) {
if ($row['id'] == $params[0]) {
return $row;
}
}
return null;
}
public function insert($table, $data) {
$this->data[] = $data;
}
}
class UserRepositoryTest extends \PHPUnit\Framework\TestCase {
public function testFindUserById() {
$fakeDb = new FakeDatabase();
$fakeDb->insert('users', ['id' => 1, 'name' => 'John Doe']);
$repository = new UserRepository($fakeDb);
$user = $repository->findUserById(1);
$this->assertEquals('John Doe', $user['name']);
}
}