Test doubles: Difference between revisions

From Freephile Wiki
Created page with " 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. There are 5 types of Test doubles <ul> <li> Dummy - "A dummy argument" - used as a placeholder when an argument needs to be passed in. <li> Stub - Provides fake or 'canned' data to the System Under Test (<abbr title="System Under Test is the class tha..."
 
No edit summary
Line 3: Line 3:


There are 5 types of Test doubles
There are 5 types of Test doubles
<ul>
 
<li> Dummy - "A dummy argument" - used as a placeholder when an argument needs to be passed in.
; Dummy
<li> Stub - Provides fake or 'canned' data to the System Under Test (<abbr title="System Under Test is the class that the Automated Test is targeting">SUT</abbr>).  
: "A dummy argument" - used as a placeholder when an argument needs to be passed in.
<li> Spy - Records information about how it is used, and can provide that information back to the test.
; Stub
<li> 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.
: Provides fake or 'canned' data to the System Under Test (<abbr title="System Under Test is the class that the Automated Test is targeting">SUT</abbr>).  
<li> Fake - "Looks real, but it's not." An actual (simplified) implementation of the contract, unsuitable for production.  
; Spy  
</ul>
: 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.  
 





Revision as of 15:52, 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.

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']);
    }
}