# Unit Testing

Unit Testing is defined as a type of software testing where **units**, individual components of a software, are tested. These may for example be a single function or a procedure. Unit tests are written to make sure that isolated pieces of functionality work as expected outside of the context of the rest of the application.
[TOC]
## Basics
1. The name of the test class has to **mimic** the name of the tested class. To test CSVImporter, you would create the CSVImporterTest class.
2. The test class **extends** the PHPUnit_Framework_TestCase class or the TestCase class. This gives you access to built-in functionality.
3. The tests are public methods whose name start with **test** and end with a desired behaviour, e.g.: test_csvValid_shouldWork().
4. Inside the test methods, the **AAA** approach has to be followed:
* **Arrange**: Bring the system under test in the desired state. Prepare dependencies, arguments and finally construct the SUT.
* **Act**: Invoke a tested element.
* **Assert**: Verify the result, the final state, or the communication with collaborators.
:::success
**Example: Testing array operations with PHPUnit**
```php=
<?
use PHPUnit\Framework\TestCase;
final class StackTest extends TestCase
{
public function testPushAndPop(): void
{
// Arrange
$stack = [];
$this->assertSame(0, count($stack));
// Act
array_push($stack, 'foo');
// Assert
$this->assertSame('foo', $stack[count($stack)-1]);
$this->assertSame(1, count($stack));
$this->assertSame('foo', array_pop($stack));
$this->assertSame(0, count($stack));
}
}
```
:::
### Assertions
Assertion methods simply are regular methods that return either true or false after evaluating the code you have passed.
:::success
```php=
<?php
// ...
public function testTrueIsTrue()
{
$foo = true;
$this->assertTrue($foo);
}
public function testFalseIsFalse()
{
$foo = false;
$this->assertFalse($foo);
}
```
:::
PHPUnit offers around 90 assertions, which are listed here: https://phpunit.readthedocs.io/en/9.5/assertions.html, but most of the time `assertEquals()`, `assertSame()`, `assertFalse()` and `assertTrue()` are used.
* `assertEquals()` reports an error identified by $message if the two variables $expected and \$actual are not equal. `$this->assertEquals(1, 0);` will return false
* `assertSame()` reports an error identified by $message if the two variables $expected and \$actual do not have the same type and value. `$this->assertSame('2204', 2204);` will return false
* `assertFalse()` reports an error identified by $message if \$condition is true. `$this->assertFalse(true);` will return false and `$this->assertFalse(false);` will return true
* `assertTrue()` reports an error identified by $message if \$condition is false. `$this->assertTrue(false);` will return false
:::success
Example: **assertEquals()**
In this non-trivial test we will create a test for a sluggify method. This method will turn a string into a URL-safe string: “This string will be sluggified” will turn into “this-string-will-be-sluggified”.
```php=
<?php
namespace phpUnitTutorial;
// Original class
class URL
{
public function sluggify($string, $separator = '-', $maxLength = 96)
{
$title = iconv('UTF-8', 'ASCII//TRANSLIT', $string);
$title = preg_replace("%[^-/+|\w ]%", '', $title);
$title = strtolower(trim(substr($title, 0, $maxLength), '-'));
$title = preg_replace("/[\/_|+ -]+/", $separator, $title);
return $title;
}
}
// Test class
class URLTest extends \PHPUnit_Framework_TestCase
{
public function test_sluggifyReturnsSluggifiedString_shouldWork()
{
//Arrange
$originalString = 'This string will be sluggified';
$expectedResult = 'this-string-will-be-sluggified';
$url = new URL();
// Act
$result = $url->sluggify($originalString);
//Assert
$this->assertEquals($expectedResult, $result);
}
public function test_sluggifyStringsContainNumbers_shouldWork()
{
$originalString = 'This1 string2 will3 be 44 sluggified10';
$expectedResult = 'this1-string2-will3-be-44-sluggified10';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
public function test_sluggifyStringsContainSpecialChars_shouldWork()
{
$originalString = 'This! @string#$ %$will ()be "sluggified';
$expectedResult = 'this-string-will-be-sluggified';
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
}
```
:::
### Data Providers
A data provider can be used to create multiple sets of information to be passed into a single test, removing the need to create multiple duplicate tests as we did above. The data provider method to be used is specified using the `@dataProvider` annotation.
:::success
```php=
<?php
class URLTest extends \PHPUnit_Framework_TestCase
{
/**
* @param string $originalString - String to be sluggified
* @param string $expectedResult - What we expect our slug result to be
*
* @dataProvider providerTestSluggifyReturnsSluggifiedString
*/
public function test_sluggifyString_shouldWork($originalString, $expectedResult)
{
$url = new URL();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
public function providerTestSluggifyReturnsSluggifiedString()
{
return array(
array('This string will be sluggified',
'this-string-will-be-sluggified'),
array('THIS STRING WILL BE SLUGGIFIED',
'this-string-will-be-sluggified'),
array('This1 string2 will3 be 44 sluggified10',
'this1-string2-will3-be-44-sluggified10'),
array('This! @string#$ %$will ()be "sluggified',
'this-string-will-be-sluggified'),
array("Tänk efter nu – förr'n vi föser dig bort",
'tank-efter-nu-forrn-vi-foser-dig-bort'),
array('', ''),
);
}
}
```
:::
:::success
Using a data provider that returns an array of arrays:
```php=
<?php
use PHPUnit\Framework\TestCase;
final class DataTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd(int $a, int $b, int $expected): void
{
$this->assertSame($expected, $a + $b);
}
public function additionProvider(): array
{
return [
[0, 0, 0],
[0, 1, 1],
[1, 0, 1],
[1, 1, 3]
];
}
// ----> Output: DataTest::testAdd with data set #3 (1, 1, 3)
// [...] Failed asserting that 2 is identical to 3.
```
Using a data provider with named datasets:
```php=
<?php
// When using a large number of datasets it’s useful to name
// each one with string key instead of default numeric. Output
// will be more verbose as it’ll contain that name of a
// dataset that breaks a test.
public function additionProvider(): array
{
return [
'adding zeros' => [0, 0, 0],
'zero plus one' => [0, 1, 1],
'one plus zero' => [1, 0, 1],
'one plus one' => [1, 1, 3]
];
}
// ----> Output: DataTest::testAdd with data set "one plus one" (1, 1, 3)
// [...] Failed asserting that 2 is identical to 3.
```
:::
More examples: https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#data-providers
### Testing Exceptions
PHPUnit can test exceptions, using the exceptException() method. The example below illustrates the use of the exceptException() method to test for an exception against a SUT (system under test).
:::success
```php=
<?php
public function test_accessInvalidCredentials_shouldThrowInvalidCredentialsException()
{
$sut = new HTTPSDownloader(HTTPS_BASIC_AUTH_PATH, AUTH_USER, "falsepass");
$filename = $this->temp_path . "/test.txt";
$this->expectException(DownloadFailedException::class);
$sut->download($filename);
}
```
:::
:::success
Example: **Email validation**
We have the Email and EmailValidator classes. Email is a value object that makes sure it is a valid email. We use the EmailValidator to make sure that the emails are only from our company.
```php=
<?php
// Original Class
//Email.php
final class Email
{
private function __construct(
private string $email
){}
public static function create(string $email): self
{
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException(sprintf(
'"%s" is not a valid email',
$email
));
}
return new self($email);
}
public function asString(): string
{
return $this->email;
}
}
//EmailValidator.php
final class EmailValidator
{
public function validateCompanyEmail(Email $email): void
{
if (! str_ends_with($email->asString(), '@company.com')) {
throw new InvalidArgumentException(
'Only "@company.com" emails are allowed'
);
}
}
}
```
:::
We use the expectException method to tell PHPUnit that we expect this exception. If it is not thrown, or if another exception is thrown, then this test will fail.
:::success
```php=
<?php
// Test class
final class EmailValidatorTest extends TestCase
{
// A test that makes sure we don’t allow emails other than @company.com.
public function test_validateEmail_shouldWork(): void
{
$email = Email::create('foo@bar.com');
$validator = new EmailValidator();
$this->expectException(InvalidArgumentException::class);
$validator->validateCompanyEmail($email);
}
}
```
:::
By starting with the expectException, the test will pass as long the InvalidArgumentException is thrown anywhere within this test. So if we make a typo, and pass foo@barcom to the Email::create, an exception will be thrown. And the test will pass. But then we never test the EmailValidator. So, to make sure we are properly testing the exception we need to call expectException just before the method that throws as in the code above. The example below shows how testing the validateCompanyEmail cannot be tested.
:::danger
```php=
<?php
public function test_validateEmail_shouldWork(): void
{
$this->expectException(InvalidArgumentException::class);
$email = Email::create('foo@bar.com');
$validator = new EmailValidator();
$validator->validateCompanyEmail($email);
}
}
```
:::
The validateCompanyName() method may throw exceptions for different reasons. For example, lets say our email validator also makes sure that our colleague bob can’t log in. So the method would look like this:
:::success
```php=
<?php
public function validateCompanyEmail(Email $email): void
{
if (! str_ends_with($email->asString(), '@company.com')) {
throw new InvalidArgumentException(
'Only "@company.com" emails are allowed'
);
}
if ($email->asString() === 'bob@company.com') {
throw new InvalidArgumentException(
'bob is no longer allowed to log in'
);
}
}
```
:::
Now we need to test the second path like in the example below. But we forgot to turn bar.com into company.com. The test is still green, but we don’t actually test that bob can’t log in.
:::danger
```php=
<?php
public function testBobCantLogIn(): void
{
$email = Email::create('bob@bar.com');
$validator = new EmailValidator();
$this->expectException(InvalidArgumentException::class);
$validator->validateCompanyEmail($email);
}
```
There are a few options here.
* We could introduce different exception classes for each error state.
* We can use expectExceptionMessage to make sure we get the correct error. So if we add `$this->expectExceptionMessage('bob is no longer allowed to log in');` just after the expectException call, then our test turns red. If we then change @bar.com to @company.com, our test turns green again.
:::
The best practice would be to do both. Introducing new exception classes per error means we can more easily catch the right error. Validating you get the right error message is really important if that message faces users. But even if it is only for other devs, you want to make sure you get the correct exception.
#### Testing Exceptions with data
Sometimes we use exceptions to pass data back. Perhaps a validator that can have multiple failures, and that has an array of all the errors. Lets take a look at the FormValidator class, and how we would test it. This class validates some information, and may throw an exception, containing the data of all things that went wrong.
:::success
```php=
<?php
final class FormValidator
{
public function validate(
DateTimeImmutable $start,
DateTimeImmutable $end,
int $newId,
string $description
): void {
$errors = [];
if ($start > $end) {
$errors[] = 'End must be after start';
}
if ($newId <= 0) {
$errors[] = 'The new id must be greater than 0';
}
if ($description === '') {
$errors[] = 'Description can not be empty';
}
if ($errors !== []) {
throw FormValidationException::fromValidationErrors($errors);
}
}
}
```
:::
If we need to test this, using expectException doesn’t make much sense. expectExceptionMessage wont help us out either, as it doesn’t have a message. Instead we can use the good only try catch here.
:::success
```php=
<?php
final class FormValidatorTest extends TestCase
{
public function testValidatesMultipleErrors(): void
{
$validator = new FormValidator();
try {
$validator->validate(
new DateTimeImmutable('2020-01-01'),
new DateTimeImmutable('1999-01-01'),
-3,
''
);
$this->fail('FormValidationException was not thrown');
} catch (FormValidationException $e) {
$this->assertSame(
[
'End must be after start',
'The new id must be greater than 0',
'Description can not be empty',
],
$e->getErrors()
);
}
}
}
```
Important here is the fail after the validation. If the method throws no exception, we need to fail the test. If we don’t add that call then our test would pass if no exception was thrown.
But, if you don’t need to check any details, other than the exception class, message or code, use expectException.
:::
### Set up and tear down
PHPUnit supports sharing the setup code. Before a test method is run, a template method called `setUp()` is invoked. We use the `setUp()` method to initialise variables, open file connection etc, that will prepare the environment for the test. And after completing the test we use the tearDown() method to unset variables, close file connection etc.
In the following example we are testing odd/even number. Our test methods are `testOdd()` and `testEven()`. We also have the `setUp()` method that is executed before every test and `tearDown()` method that is executed after every test. We also have a private variable $number. And in the `setUp()` method we are assigning value 2 to the variable $number.
:::success
```php=
<?php
class OddEvenTest extends PHPUnit_Framework_TestCase
{
private $number;
protected function setUp()
{
$this->number = 2;
}
public function testOdd()
{
$this->number++;
$this->assertNotEquals(0, $this->number % 2);
}
public function testEven()
{
$this->assertEquals(0, $this->number % 2);
}
protected function tearDown()
{
$this->number = null;
}
}
```
:::
The `setUpBeforeClass()` method is called before the first test is executed and the `tearDownAfterClass()` method is called after last test is executed. We use these two methods to share settings with all the tests. For example, we can create a database connection only once and then reuse the connection in every test. This helps to run the test faster.
## Test Doubles
http://blog.nona.digital/mocking-in-phpunit/
### Creating test double objects
Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn’t have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one.
:::success
Example: **Payment**
```php=
<?php
namespace phpUnitTutorial;
class Payment
{
const API_ID = 123456;
const TRANS_KEY = 'TRANSACTION KEY';
public function processPayment(array $paymentDetails)
{
$transaction = new \AuthorizeNetAIM(self::API_ID, self::TRANS_KEY);
$transaction->amount = $paymentDetails['amount'];
$transaction->card_num = $paymentDetails['card_num'];
$transaction->exp_date = $paymentDetails['exp_date'];
$response = $transaction->authorizeAndCapture();
if ($response->approved) {
return $this->savePayment($response->transaction_id);
} else {
throw new \Exception($response->error_message);
}
}
public function savePayment($transactionId)
{
// Logic for saving transaction ID e.g. to database
return true;
}
}
```
:::
:::danger
For testing, we set up the `$paymentDetails` array and now that we have the required parameter we can instantiate the object, pass in the array and set our expected result - a return value of true. But using the code below we will get an error because the connection could not have been started.
```php=
<?php
namespace phpUnitTutorial\Test;
use phpUnitTutorial\Payment;
class PaymentTest extends \PHPUnit_Framework_TestCase
{
public function testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$result = $payment->processPayment($paymentDetails);
$this->assertTrue($result);
}
}
```
:::
PHPUnit comes with a very powerful feature to help us handle these problems. It basically involves replacing the actual object with a fake, or ‘mock’, object that we fully control, removing all dependencies on outside systems or code that we really have no need to test.
The **createStub(\$type)**, **createMock(\$type)**, and **getMockBuilder(\$type)** methods provided by PHPUnit can be used in a test to automatically generate an object that can act as a test double for the specified original type (interface or class name). This test double object can be used in every context where an object of the original type is expected or required.
* The **createMock(\$type)** method returns a test double object for the specified type (interface or class).
* The **createStub(\$type)** method also returns a test double object.
* The **getMockBuilder(\$type)** method can be used to customize the test double generation if these defaults are not what you need. It returns a *mock object*, which is simply an object that has behavior similar to the original object.
:::success
```php=
<?php
$authorizeNet = $this->getMockBuilder('\AuthorizeNetAIM')
->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY))
->getMock();
```
:::
By default, all methods of the original class are replaced with a dummy implementation that returns null (without calling the original method). These methods are considered stubs. Using the `will($this->returnValue())` method, for instance, you can configure these dummy implementations to return a value when called.
:::success
```php=
<?php
$authorizeNet->expects($this->once())
->method('authorizeAndCapture')
->will($this->returnValue('RETURN VALUE HERE!'));
```
:::
Note that final, private, and static methods cannot be stubbed or mocked. They are ignored by PHPUnit’s test double functionality and retain their original behavior except for static methods that will be replaced by a method throwing an exception.
:::success
Running our test now will still fail, because `authorizeAndCapture()` is returning a string, when our code is expecting an object with an approved and transaction_id key. A simple shortcut for these types of objects is to use `\stdClass()`.
```php=
<?php
namespace phpUnitTutorial\Test;
use phpUnitTutorial\Payment;
class PaymentTest extends \PHPUnit_Framework_TestCase
{
public function testProcessPaymentReturnsTrueOnSuccessfulPayment()
{
$paymentDetails = array(
'amount' => 123.99,
'card_num' => '4111-1111-1111-1111',
'exp_date' => '03/2013',
);
$payment = new Payment();
$response = new \stdClass();
$response->approved = true;
$response->transaction_id = 123;
$authorizeNet = $this->getMockBuilder('\AuthorizeNetAIM')
->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY))
->getMock();
$authorizeNet->expects($this->once())
->method('authorizeAndCapture')
->will($this->returnValue($response));
$result = $payment->processPayment($authorizeNet, $paymentDetails);
$this->assertTrue($result);
}
}
```
:::
:::success
Example: **Greeting a user using ID from DB**
```php=
<?php
class Person
{
public $db = null;
function __construct($db)
{
$this->db = $db;
}
public function greeting($id)
{
$friend = $this->db->getPersonByID($id);
$friendName = $friend->name;
return "Hello $friendName";
}
require __DIR__ . '/Database.php';
$db = new Database();
$person = new Person($db);
// in our database we have a person with an ID of 2 and a name of "Bob"
echo $person->greeting(2);
// result should be "Hello Bob"
}
```
We don’t want to test that `getPersonByID` returns a result from our database, but instead that `person->greeting(2)` gives us a result that we expect.
```php=
<?php
require __DIR__ . '/Database.php';
require __DIR__ . '/Person.php';
use PHPUnit\Framework\TestCase;
class MockTest extends TestCase
{
public function test_greeting()
{
$dbMock = $this->getMockBuilder(Database::class);
$dbMock->method('getPersonByID')->willReturn($mockPerson);
$mockPerson = new stdClass();
$mockPerson->name = 'Bob';
$test = new Person($dbMock);
$this->assertEquals('Hello Bob', $test->greeting(2));
}
}
```
Firstly we want to build a mock of the Database class that is exactly the same except we are telling the MockBuilder that we are going to get setting the getPersonByID later on ourselves. `$dbMock` now becomes our `$db`. Next we create a “fake” person object returned from the db that is just a standard class type with a name attribute of “Bob”. Then we pass our mocked Database instance into our Person constructor as opposed to a real one. Finally we call our greeting function which will in turn call our mocked Database instance during its execution. (http://blog.nona.digital/mocking-in-phpunit/)
:::
### Mocks vs Stubs
**Behavioral testing vs State testing**
Meszaros uses the term Test Double as the generic term for any kind of pretend object used in place of a real object for testing purposes. The name comes from the notion of a Stunt Double in movies. Meszaros then defined particular kinds of doubles:
* *Dummy* objects are passed around but never actually used. Usually they are just used to fill parameter lists. A dummy object is the object you get if you add an argument with a type-hint in phpspec to get a test double... then do absolutely nothing with it. So if we get a test double and add no behavior and make no assertions on its methods, it's called a "dummy object".
* *Stubs* provide canned answers to calls made during the test, As soon as you start controlling even one return value of even one method... boom! This object is suddenly known as a stub. As soon as we add one of these willReturn() things, it becomes a stub.
* *Mocks* are objects pre-programmed with expectations which form a specification of the calls they are expected to receive. Of these kinds of doubles, only mocks insist upon behavior verification. Mocks actually do behave like other doubles during the exercise phase, as they need to make the SUT believe it's talking with its real collaborators - but mocks differ in the setup and the verification phases. An object becomes a mock when you call shouldBeCalled(). So, if you want to add an assertion that a method is called a certain number of times and you want to put that assertion before the actual code - using shouldBeCalledTimes() or shouldBeCalled(), then you are using a mock object.
:::success
A common case for a test double would be sending an email message if we failed to fill an order. The problem is that we don't want to send actual email messages out to customers during testing. So instead we create a test double of our email system, one that we can control and manipulate. Here we can begin to see the difference between mocks and stubs. If we were writing a test for this mailing behavior, we might write a simple stub like this.
```java=
public interface MailService {
public void send (Message msg);
}
public class MailServiceStub implements MailService {
private List<Message> messages = new ArrayList<Message>();
public void send (Message msg) {
messages.add(msg);
}
public int numberSent() {
return messages.size();
}
}
// We can then use state verification on the stub like this.
class OrderStateTester...
public void test_OrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceStub mailer = new MailServiceStub();
order.setMailer(mailer);
order.fill(warehouse);
assertEquals(1, mailer.numberSent());
}
```
Of course this is a very simple test - only that a message has been sent. Using mocks this test would look quite different.
```java=
class OrderInteractionTester...
public void test_OrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
```
In both cases I'm using a test double instead of the real mail service. There is a difference in that the stub uses state verification while the mock uses behavior verification.
:::
In order to use state verification on the stub, I need to make some extra methods on the stub to help with verification. As a result the stub implements MailService but adds extra test methods.
Both mocks and stubs testing give an answer for the question: What is the result? Testing with mocks are also interested in: How the result has been achieved?
https://martinfowler.com/articles/mocksArentStubs.html
### Stubs
A stub is a controllable replacement for an Existing Dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly. It encapsulates the complexity of invoking another program (usually located on another machine, VM, or process - but not always, it can also be a local object).
:::success
The example below shows how to stub method calls and set up return values. We first use the createStub() method to set up a stub object that looks like an object of SomeClass. We then use the Fluent Interface from PHPUnit provides to specify the behavior for the stub. In essence, this means that you do not need to create several temporary objects and wire them together afterwards. Instead, you chain method calls as shown in the example. This leads to more readable and “fluent” code.
```php=
<?php
// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);
// Configure the stub.
$stub
->method('doSomething')
->willReturn('foo');
// Calling $stub->doSomething() will now return 'foo'.
$this->assertEquals('foo', $stub->doSomething());
```
:::
This phrase is almost certainly an analogy with a phase in house construction — "stubbing out" plumbing. During construction, while the walls are still open, the rough plumbing is put in. This is necessary for the construction to continue. Then, when everything around it is ready enough, one comes back and adds faucets and toilets and the actual end-product stuff.
When you "stub out" a function in programming, you build enough of it to work around (for testing or for writing other code). Then, you come back later and replace it with the full implementation.
In the example above we have been returning simple values using `willReturn($value)` – a short syntax for convenience. Available stubbing short hands alongside their longer counterparts can be found on https://phpunit.readthedocs.io/en/9.5/test-doubles.html#stubs

### Mocks
A mock is a very specific and restrictive kind of stub, because a mock is a replacement of another function or object for testing. In practice we often use mocks as local programs (functions or objects) to replace a remote program in the test environment. In any case, the mock may simulate the actual behaviour of the replaced program in a restricted context.
:::success
```php=
<?php
```
:::
You can use a mock object “as an observation point that is used to verify the indirect outputs of the SUT as it is exercised. Typically, the mock object also includes the functionality of a test stub in that it must return values to the SUT if it hasn’t already failed the tests but the emphasis is on the verification of the indirect outputs.
:::success
Example: suppose we want to test that the correct method, update() in our example, is called on an object that observes another object. Example 8.11 shows the code for the Subject and Observer classes that are part of the System under Test (SUT).
```php=
<?php
use PHPUnit\Framework\TestCase;
class Subject
{
protected $observers = [];
protected $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function attach(Observer $observer)
{
$this->observers[] = $observer;
}
public function doSomething()
{
// Do something.
// ...
// Notify observers that we did something.
$this->notify('something');
}
public function doSomethingBad()
{
foreach ($this->observers as $observer) {
$observer->reportError(42, 'Something bad happened', $this);
}
}
protected function notify($argument)
{
foreach ($this->observers as $observer) {
$observer->update($argument);
}
}
// Other methods.
}
class Observer
{
public function update($argument)
{
// Do something.
}
public function reportError($errorCode, $errorMessage, Subject $subject)
{
// Do something
}
// Other methods.
}
```
:::
The example below shows how to use a mock object to test the interaction between Subject and Observer objects.
* We first use the createMock() method to set up a mock object for the Observer.
* Because we are interested in verifying that a method is called, and which arguments it is called with, we introduce the expects() and with() methods to specify how this interaction should look.
* The with() method can take any number of arguments, corresponding to the number of arguments to the method being mocked. You can specify more advanced constraints on the method’s arguments than a simple match.
:::success
```php=
<?php
use PHPUnit\Framework\TestCase;
final class SubjectTest extends TestCase
{
// Testing that a method gets called once and with a specified argument
public function testObserversAreUpdated(): void
{
// Create a mock for the Observer class,
// only mock the update() method.
$observer = $this->createMock(Observer::class);
// Set up the expectation for the update() method
// to be called only once and with the string 'something'
// as its parameter.
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
// Create a Subject object and attach the mocked
// Observer object to it.
$subject = new Subject('My subject');
$subject->attach($observer);
// Call the doSomething() method on the $subject object
// which we expect to call the mocked Observer object's
// update() method with the string 'something'.
$subject->doSomething();
}
// Testing that a method gets called with a number of arguments
// constrained in different ways
public function testErrorReported(): void
{
// Create a mock for the Observer class, mocking the
// reportError() method
$observer = $this->createMock(Observer::class);
$observer->expects($this->once())
->method('reportError')
->with(
$this->greaterThan(0),
$this->stringContains('Something'),
$this->anything()
);
$subject = new Subject('My subject');
$subject->attach($observer);
// The doSomethingBad() method should report an error to the observer
// via the reportError() method
$subject->doSomethingBad();
}
}
```
:::
More examples: https://phpunit.readthedocs.io/en/9.5/test-doubles.html#mock-objects
### Errors

https://phpunit.readthedocs.io/en/9.5/test-doubles.html
https://www.c-sharpcorner.com/UploadFile/dacca2/understand-stub-mock-and-fake-in-unit-testing/
https://github.com/concrete5/concrete5/pull/4283