Test doubles: Difference between revisions

move and separate footnotes
 
(3 intermediate revisions by the same user not shown)
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><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>.
A '''[[wp:Test double]]''' is any object that stands in for a real dependency in automated testing<ref>
'''Important Material'''
 
see [https://docs.phpunit.de/en/9.6/test-doubles.html Test Doubles in PHPUnit (v9.6)] or ([https://docs.phpunit.de/en/10.5/test-doubles.html v10.5])
 
see the 'original' '''book on XUnit''' - [http://xunitpatterns.com/Using%20Test%20Doubles.html  chapter on Test Doubles], including diagrams to help illustrate the various types of doubles</ref>. 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><ref>These 'other things' being doubled are sometimes referred to as the "Depended On Component"</ref>.


There are 5 types of Test doubles
There are 5 types of Test doubles
Line 16: Line 21:




{{Ambox|
|type=notice
|text=Favour doubling interfaces over doubling classes
Not only because of the limitations on what can be doubled (final, private, static methods can not be doubled), but also to improve your software design, favour the doubling of interfaces over the doubling of classes.
}}


== Dummy ==
== Dummy ==
Line 39: Line 51:


== Stub ==
== 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 stub is an object that provides predefined responses to method calls. It is used to control the behavior of the System Under Test, "steering it" in a particular direction.
In the example below, the '''Logger->log()''' method does some stuff with a message. For our test scenario, we use a Stub of the Logger class where we define the <code>log()</code> method to always return 'true'.  Why do that? Since the log method always returns true, we can focus our test on another aspect of the System Under Test that ''relies on or is dependent on'', the log method returning true. (Sorry that no actual example is given in the unimaginative code below. Supose you had a system that detects outdoor temperature and tells you when it is safe to skate on the ice. You could force (stub) the temp detection to read "freezing" to assert that the signal "It is safe to skate" is given by the SUT.)


<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
<?php
class User {
class User {
     public function __construct(private Logger $logger) {}
     public function __construct(private Logger $logger) {}
Line 46: Line 64:
     public function logMessage($message) {
     public function logMessage($message) {
         return $this->logger->log($message);
         return $this->logger->log($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'));
     }
     }
}
}


</syntaxhighlight>
</syntaxhighlight>
=== PHPUnit Mock Builder ===
In PHPUnit, you can simply call <code>$this->createStub(SomeClass::class)</code>, but you can also be more refined [https://docs.phpunit.de/en/9.6/test-doubles.html#test-doubles-stubs-examples-stubtest2-php using the Mock Builder fluent interface].




== Spy ==
== Spy ==
Records information about how it is used, and can provide that information back to the test.
A spy is a test double that records information about the interactions with it, such as method calls and parameters. It is used to verify that certain interactions occurred.
Notice in the example below, the code is ordered differently from the preferred 'AAA' Test Pattern of '''A'''rrange, '''A'''ct, '''A'''ssert<ref>https://wiki.c2.com/?ArrangeActAssert</ref>. Below, we Arrange, Assert, then Act.
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
class User {
class User {
Line 79: Line 122:
     }
     }
}
}
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'));
    }
}
</syntaxhighlight>
</syntaxhighlight>


Line 141: Line 167:


== Fake ==
== Fake ==
A fake is a type of test double that has a working implementation but is simplified and not suitable for production. Fakes are often used to simulate complex systems or dependencies in a controlled way, allowing tests to run quickly and deterministically.
Example: Imagine you have a class that interacts with a database. Instead of using a real database in your tests, you might use an in-memory database or a simple array to simulate the database operations.
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">


Line 186: Line 216:


</syntaxhighlight>
</syntaxhighlight>
=== getMockFromWsdl() ===
Although PHPUnit 9.x allows you to build a mock for a web service from a [[wp:WSDL]] file, the <code>getMockFromWsdl()</code> method is deprecated in current versions and removed in PHPUnit 12. This is because people don't use SOAP and WSDL as commonly as they used to, and even if they do, they don't rely on PHP's SOAP extension <ref>[https://github.com/sebastianbergmann/phpunit/issues/5242 Deprecate TestCase::getMockFromWsdl()]</ref>.
A bit sad, but understandable. PHPUnit can't keep 20 years of legacy implementations along with new technologies.
In PHPUnit 9.6, you can easily mock a Google Web Search<ref>https://docs.phpunit.de/en/9.6/test-doubles.html#stubbing-and-mocking-web-services</ref>
{{References}}


[[Category:Development]]
[[Category:Development]]
[[Category:Testing]]
[[Category:Testing]]