Practical PHP Testing Patterns: Delta Assertion
The Web Dev Zone is brought to you in partnership with Mendix. Discover how IT departments looking for ways to keep up with demand for business apps has caused a new breed of developers to surface - the Rapid Application Developer.
Sometimes we do not know the initial state of the fixture we are going to use for a test. In this case, it's likely to be a Shared Fixture, or even simply a Standard one which we do not want to be coupled to.
In other, more complex scenarios, the fixture is non-deterministic as it has been used by previous tests, or it is generated almost randomly.
As en example, consider a database that is being filled and truncated by previous tests. Recreating it from scratch at the start of each test would be too slow. But we can't define a simple assertion, as it would depend on the initial conditions.
Instead, we write a Delta Assertion, which calculates the changes in the state of the SUT between the test start and the assert phase. It is this delta that is subjected to checks, instead of the whole final state. In mathematics, delta is a common term for indicating a change in a variable's value
The result is that you are less dependent on the data contained in the fixture: your tests are less fragile. When using Standard Fixtures instead of Minimal ones, this pattern is particularly handy as it prevents a bunch of your tests from failing just because a colleague has updated the fixture to add other tests.
In our example, we will consider testing a UserDao (or UserRepository, it's the same), that hides a relational table where users are stored. This are the phases of such a test if it employs a Delta Assertion:
- arrange: you count N rows in the corresponding User database table. This number is saved in a local variable, or in some data structure.
- act: you add a User to the database. This is quite normal.
- assert: you check that there are N+1 rows in the table.
In PHP we have usually no concurrency problems in running Test Suites, unless you are running more than one at the same time. Delta Assertions do not solve these issues automatically anyway. However, the state shared between tests shouldn't even be visible by different PHP processes.
A caveat of this pattern is that the snapshot of the initial state should not be touched by the SUT. If it consists of values (scalars), like a count() done in our example, you're safe.
But when using objects, sometimes it's useful to clone them or perform some sort of copy of their relevant fields. For example, I remember assertions made over a Query object fail because the SUT modified the Query passed in that, and the original Query object stored in a variable was actually the same object: there were no differences between the starting state and the final one.
Thus keep in mind that Delta Assertions are a clever solution to a problem that most of the time you don't have: if you isolate your objects well, you'll know their initial state and their final one deterministically.
As I've said earlier, this example shows how to test a UserDao with a in-memory database. We do not make assumptions on the content of the users table, so that if you want to move the setUp() in your bootstrap or somewhere else, the test would still pass.
class DeltaAssertionTest extends PHPUnit_Framework_TestCase
public function setUp()
// imagine that this fixture is a Shared one
// we wouldn't know the particular state of the table
$this->connection = new PDO('sqlite::memory:');
$this->connection->exec('CREATE TABLE users (nickname VARCHAR(255) NOT NULL PRIMARY KEY, password VARCHAR(255))');
public function testAnUserIsAdded()
$userDao = new UserDao($this->connection);
$previous = $userDao->countUsers();
// if the nickname is not unique, however, the test would still fail
// but we are covered against all other records inserted in the table
$this->assertEquals($previous + 1, $userDao->countUsers());
public function __construct(PDO $connection)
$this->connection = $connection;
public function countUsers()
$stmt = $this->connection->query('SELECT COUNT(*) FROM users');
$result = $stmt->fetchAll();
public function addUser($nickname, $password)
$stmt = $this->connection->prepare('INSERT INTO users (nickname, password) VALUES (:nickname, :password)');
$stmt->bindValue('nickname', $nickname, PDO::PARAM_STR);
$stmt->bindValue('password', $password, PDO::PARAM_STR);