Advanced Fixtures in PHPUnit: Setup and Global State
Introduction
#More setUp() than tearDown()
In PHPUnit `setUp()` and `tearDown()` are symmetrically used in theory but not in practice. In actual practice the `tearDown()` becomes really necessary to implement when we have allocated resources like files and sockets in the `setUp()` function.
In the case where the contents of our `setup()` are just plain PHP objects, the `tearDown()` function can generally be ignored. However, in a situation where many objects were created in the `setUp()` function, we might as well want to upset some of these variables pointing to these objects in the `tearDown()` function so they can be garbage collected.
It should be noted that the garbage collection of test case objects is not predictable.
#Variations
In a situation where we have two or more tests, whose setups differ slightly, what happens? PHPUnit suggests two possible solutions for this kind of scenario, which are
- In a situation where we have two or more tests, whose setups differ slightly, what happens? PHPUnit suggests two possible solutions for this kind of scenario, which are
- When the setup is really different, we need a different test case class. PHPUnit suggests we name the class after the difference in the setup.
#Sharing Fixture
In some cases, we might need some features to be shared between tests for some good reasons, sometimes it could be due to some unresolved design patterns.
A databases connection is a good example of a fixture that makes sense to share across several tests: you log into the database once and reuse the database connection instead of creating a new connection for each test. This will make our tests run faster.
The example below illustrates the sharing of a database connection across several tests. It uses the `setUpBeforeClass()` and `tearDownAfterClass()` template methods to connect to the database before the test case class’ first test and to disconnect from the database after the last test of the test case, respectively.
<?php
use PHPUnit\Framework\TestCase;
class DatabaseTest extends TestCase
{
protected static $dbh;
public static function setUpBeforeClass(): void
{
self::$dbh = new PDO('sqlite::memory:');
}
public static function tearDownAfterClass(): void
{
self::$dbh = null;
}
}
?>
PHPUnit emphasizes that sharing fixtures between tests reduces the value of these tests. It emphasizes the need for objects to be loosely coupled, as sharing fixtures encourages bad program design. It encourages writing tests with stubs and a good program design, geared towards loosely coupled programs.
#Global State
It has been researched that it is difficult to test code that uses singletons. This difficulty is also true for code that uses global variables. Typically, the code we want to test is coupled strongly with a global variable and we cannot control its creation. An additional problem is the fact that one test’s change to a global variable might break another test.
Global variables in PHP works like this:
- A global variable `$foo = 'bah';` is stored as `$GLOBALS['foo'] = 'bah ';`.
- The $GLOBALS variable 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 $foo by either directly accessing $GLOBALS['foo'] or by using `global $foo;` to create a local variable with a reference to the global variable.
Besides global variables, static attributes of classes are also part of the global state.
In the earlier versions of PHP, by default PHPUnit runs tests in such a way that changes to global and super-global variables ($GLOBALS, $_ENV, $_POST, $_GET, $_COOKIE, $_SERVER, $_FILES, $_REQUEST) have no effect on other tests.
But as of PHP version 6, the backup and restore operation on global and super-global variables are no longer performed by PHPUnit by default. It can now be activated by using the --globals-backup option or setting backupGlobals="true" in the XML configuration file.
By using the --static-backup option or setting backupStaticAttributes="true" in the XML configuration file, this isolation can be extended to static attributes of classes.
Note:
The serialize() and unserialize() methods are used in the backup for global variable and static attributes of a class.
Some classes like `PDO` do not support the serialization of it objects. Thus, the backup operation breaks when such objects are stored in the $GLOBAL array
backup and restore operations for global variables can be controlled using the @backupGlobals annotation will is discussed in @backupGlobals section. Alternatively,
global variables can be blacklisted and thus will be excluded from the backup and restore operations simply by providing a list as shown below:
class MyTest extends TestCase
{
protected $backupGlobalsBlacklist = ['globalVariable'];
// ...
}
Note
Setting the `$backupGlobalsBlacklist` property inside example the `setUp()` method has no effect.
The `@backupStaticAttributes` annotation to be discussed in @backupStaticAttributes can be used to back up all static property values in all declared classes before each test and restore them afterwards.
It processes all classes that are declared at the time a test starts, not only the test class itself. It only applies to static class properties, not static variables within functions.
Note
The @backupStaticAttributes operation is executed 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 @backupStaticAttributes enabled, then that value will be backed up and restored — not the originally declared default value. PHP does not record the originally declared default value of any static variable.
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).
We can provide a blacklist of static attributes that are to be excluded from the backup and restore operations:
class MyTest extends TestCase
{
protected $backupStaticAttributesBlacklist = [
'className' => ['attributeName']
];
// ...
}
Setting the $backupStaticAttributesBlacklist property inside e.g. the setUp() method also has no effect.
Previous:
Test Fixtures in PHPUnit: SetUp and TearDown Explained.
Next:
Organizing Tests in PHPUnit: Best Practices for Test Suites.
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://www.w3resource.com/php/PHPUnit/more-on-fixtures.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics