5. Fixtures
A test usually follows the “Arrange, Act, Assert” structure: arranging all necessary preconditions and inputs (the so-called test fixture), acting on the object under test, and asserting that the expected results have occurred.
Arrange, Expect, Act
When you expect an action to raise an exception or when you verify the communication between collaborating objects using mock objects then the test usually follows the “Arrange, Expect, Act” structure.
Sometimes the test fixture is made up of a single object, sometimes it is a more complex object graph, for instance. The amount of code needed to set it up will grow accordingly. The actual content of the test gets lost in the noise of setting up the test fixture. This problem gets even worse when you write several tests with similar test fixtures.
PHPUnit supports the reuse of setup code between tests. Before a test method is run, a
template method named setUp() is invoked: this is where you can create your test
fixture. Once the test method has finished running, whether it succeeded or failed,
another template method named tearDown() is invoked: this is where you can clean
up the objects against which you tested.
<?php declare(strict_types=1);
namespace example;
use PHPUnit\Framework\TestCase;
final class ExampleTest extends TestCase
{
private ?Example $example;
public function testSomething(): void
{
$this->assertSame(
'the-result',
$this->example->doSomething()
);
}
protected function setUp(): void
{
$this->example = new Example(
$this->createStub(Collaborator::class)
);
}
protected function tearDown(): void
{
$this->example = null;
}
}
The setUp() and tearDown() template methods are run once for each test method
(and on fresh instances) of the test case class.
Template Methods
setUp() and tearDown() are actually two of six template methods that PHPUnit calls during the lifecycle of a test case class.
Here is the complete execution order:
setUpBeforeClass() Once before the first test of the class
├── setUp() Before each test
│ ├── assertPreConditions()
│ │ ├── test method
│ │ assertPostConditions()
│ tearDown() After each test
tearDownAfterClass() Once after the last test of the class
setUpBeforeClass() and tearDownAfterClass() are called once for the entire test case class (see Sharing Fixture).
The other four template methods are called for each test method.
assertPreConditions() and assertPostConditions() are explained below.
For each of these template methods, an equivalent attribute is available that allows you to configure multiple methods for the same phase, with optional priority ordering. See Using Attributes Instead of Template Methods for details.
One problem with the setUp() and tearDown() template methods is that they are called even for tests that do not use the test fixture managed by these methods, in the example shown above the $this->example property.
Another problem can occur when inheritance comes into play:
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
abstract class MyTestCase extends TestCase
{
protected function setUp(): void
{
// ...
}
}
<?php declare(strict_types=1);
namespace example;
use PHPUnit\Framework\TestCase;
final class ExampleTest extends MyTestCase
{
protected function setUp(): void
{
// ...
}
}
If we forget to call parent::setUp() when implementing ExampleTest::setUp(), the functionality provided by MyTestCase will not work. To reduce this risk, attributes such as PHPUnit\Framework\Attributes\Before and PHPUnit\Framework\Attributes\After are available.
With these, multiple methods can be configured to be called before and after a test, respectively, without having to call parent::setUp().
See Using Attributes Instead of Template Methods for details and examples.
More setUp() than tearDown()
setUp() and tearDown() are nicely symmetrical in theory, but not in practice.
In practice, you only need to implement tearDown() if you have allocated external
resources such as files or sockets in setUp(). Unless you create large object graphs
in your setUp() and store them in properties of the test object, you can generally
ignore tearDown().
However, if you create large object graphs in your setUp() and store them in properties
of the test object, you may want to unset() the variables holding those objects in your
tearDown() so that they can be garbage collected sooner.
Objects created within setUp() (or test methods) that are stored in properties of the
test object are only automatically garbage collected at the end of the PHP process that
runs PHPUnit.
Pre-Conditions and Post-Conditions
The assertPreConditions() template method is called after setUp() but before the test method.
The assertPostConditions() template method is called after the test method but before tearDown().
These methods are intended for assertions that are shared by all tests of a test case class.
Unlike setUp() and tearDown(), a failure in assertPreConditions() or assertPostConditions() is reported as a test failure (not as an error), which means that the failure message will include assertion details.
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class SomeTest extends TestCase
{
private ?SomeObject $object;
protected function setUp(): void
{
$this->object = new SomeObject;
}
protected function assertPreConditions(): void
{
$this->assertTrue($this->object->isReady());
}
protected function assertPostConditions(): void
{
$this->assertFalse($this->object->hasErrors());
}
protected function tearDown(): void
{
$this->object = null;
}
public function testSomething(): void
{
$this->object->doSomething();
$this->assertSame('expected', $this->object->result());
}
}
In the example above, assertPreConditions() verifies the fixture is in a valid state before each test, and assertPostConditions() verifies that no errors occurred after each test.
These checks apply to all tests in the class without having to repeat them in each test method.
Using Attributes Instead of Template Methods
For each of the six template methods, PHPUnit provides a corresponding attribute that can be used to mark methods that should be called in the same phase:
Template Method |
Attribute |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
The key advantage of using attributes over template methods is that multiple methods can be configured for the same phase.
This is especially useful in class hierarchies:
each class can declare its own hook methods without having to call parent::setUp(), for example.
<?php declare(strict_types=1);
use PHPUnit\Framework\Attributes\After;
use PHPUnit\Framework\Attributes\Before;
use PHPUnit\Framework\TestCase;
abstract class MyTestCase extends TestCase
{
#[Before]
protected function setUpLogger(): void
{
// Set up logging for all tests ...
}
#[After]
protected function tearDownLogger(): void
{
// Tear down logging after all tests ...
}
}
<?php declare(strict_types=1);
use PHPUnit\Framework\Attributes\Before;
final class MyTest extends MyTestCase
{
#[Before]
protected function setUpFixture(): void
{
// Set up the fixture for this test class ...
}
public function testSomething(): void
{
// Both setUpLogger() and setUpFixture() have been called at this point
// ...
}
}
When a test case class has more than one method configured with the same attribute, the test runner assumes that the invocation order does not matter.
If the order does matter, the attribute’s optional $priority argument can be used to control it: a method with a higher $priority value is invoked before a method with a lower $priority value.
<?php declare(strict_types=1);
use PHPUnit\Framework\Attributes\Before;
use PHPUnit\Framework\TestCase;
final class PriorityTest extends TestCase
{
#[Before(priority: 2)]
protected function connectToDatabase(): void
{
// This runs first (higher priority) ...
}
#[Before(priority: 1)]
protected function seedDatabase(): void
{
// This runs second (lower priority) ...
}
}
Global State
It is hard to test code that uses singletons. The same is true for code that uses global variables. Typically, the code you want to test is coupled strongly with a global variable and you cannot control its creation. An additional problem is the fact that one test’s change to a global variable might break another test.
In PHP, global variables work like this:
A global variable
$foo = 'bar';is stored as$GLOBALS['foo'] = 'bar';.The
$GLOBALSvariable is a so-called super-global variable.Super-global variables are built-in variables that are always available in all scopes.
In the scope of a function or method, you may access the global variable
$fooby either directly accessing$GLOBALS['foo']or by usingglobal $foo;to create a local variable with a reference to the global variable.
Besides global variables, static properties of classes are also part of the global state.
PHPUnit can optionally run your tests in a way where changes to global and super-global variables
($GLOBALS, $_ENV, $_POST, $_GET, $_COOKIE, $_SERVER, $_FILES,
$_REQUEST) do not affect other tests. You can activate this behaviour by using the
--globals-backup option or by setting backupGlobals="true" in the XML configuration file.
By using the --static-backup option or setting backupStaticProperties="true" in the
XML configuration file, this isolation can be extended to static properties of classes.
Note
The backup and restore operations for global variables and static class properties use
serialize() and unserialize().
Objects of some classes (e.g., PDO) cannot be serialized and the backup operation
will break when such an object is stored e.g. in the $GLOBALS array.
The PHPUnit\Framework\Attributes\BackupGlobals attribute can be used to control the
backup and restore operations for global variables.
The PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup attribute can be used
to exclude specific global variables from the backup and restore operations for global variables.
The PHPUnit\Framework\Attributes\BackupStaticProperties attribute can be used to control
the backup and restore operations for static properties of classes. This affects all static
properties in all declared classes before each test and restore them afterwards. All classes
that are declared at the time a test starts are processed, not only the test class itself. It
only applies to static class properties, not static variables within functions.
The PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup attribute can be used
to exclude specific static properties from the backup and restore operations for static properties.
Note
The backup operation for static properties of classes is performed before a test method,
but only if it is enabled. If a static value was changed by a previously executed test that
did not have BackupStaticProperties(true), then that value will be backed up and restored —
not the originally declared default value.
The same applies to static properties of classes that were newly loaded/declared within a test. They cannot be reset to their originally declared default value after the test, since that value is unknown. Whichever value is set will leak into subsequent tests.
For unit tests, it is recommended to explicitly reset the values of static properties under test
in your setUp() code instead (and ideally also tearDown(), so as to not affect subsequently
executed tests).
The WithEnvironmentVariable attribute can be used
to set or remove environment variables for the duration of a test. Environment variables are set before
setUp() is called and restored to their original values after tearDown() has completed.