PHPUnit Manual

Sebastian Bergmann

This work is licensed under the Creative Commons Attribution 3.0 Unported License.

Edition for PHPUnit 3.6. Updated on 2012-02-11.


1. Automating Tests
2. PHPUnit's Goals
3. Installing PHPUnit
4. Writing Tests for PHPUnit
Test Dependencies
Data Providers
Testing Exceptions
Testing PHP Errors
Testing Output
Assertions
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertCount()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
5. The Command-Line Test Runner
Command-Line switches
6. Fixtures
More setUp() than tearDown()
Variations
Sharing Fixture
Global State
7. Organizing Tests
Composing a Test Suite Using the Filesystem
Composing a Test Suite Using XML Configuration
8. Database Testing
Supported Vendors for Database Testing
Difficulties in Database Testing
The four stages of a database test
1. Clean-Up Database
2. Set up fixture
3–5. Run Test, Verify outcome and Teardown
Configuration of a PHPUnit Database TestCase
Implementing getConnection()
Implementing getDataSet()
What about the Database Schema (DDL)?
Tip: Use your own Abstract Database TestCase
Understanding DataSets and DataTables
Available Implementations
Beware of Foreign Keys
Implementing your own DataSets/DataTables
The Connection API
Database Assertions API
Asserting the Row-Count of a Table
Asserting the State of a Table
Asserting the Result of a Query
Asserting the State of Multiple Tables
Frequently Asked Questions
Will PHPUnit (re-)create the database schema for each test?
Am I required to use PDO in my application for the Database Extension to work?
What can I do, when I get a Too much Connections Error?
How to handle NULL with Flat XML / CSV Datasets?
9. Incomplete and Skipped Tests
Incomplete Tests
Skipping Tests
10. Test Doubles
Stubs
Mock Objects
Stubbing and Mocking Web Services
Mocking the Filesystem
11. Testing Practices
During Development
During Debugging
12. Test-Driven Development
BankAccount Example
13. Behaviour-Driven Development
BowlingGame Example
14. Code Coverage Analysis
Specifying Covered Methods
Ignoring Code Blocks
Including and Excluding Files
Edge cases
15. Other Uses for Tests
Agile Documentation
Cross-Team Tests
16. Skeleton Generator
Generating a Test Case Class Skeleton
Generating a Class Skeleton from a Test Case Class
17. PHPUnit and Selenium
Selenium Server
PHPUnit_Extensions_SeleniumTestCase
18. Logging
Test Results (XML)
Test Results (TAP)
Test Results (JSON)
Code Coverage (XML)
Code Coverage (TEXT)
19. Extending PHPUnit
Subclass PHPUnit_Framework_TestCase
Write custom assertions
Implement PHPUnit_Framework_TestListener
Subclass PHPUnit_Extensions_TestDecorator
Implement PHPUnit_Framework_Test
A. Assertions
B. Annotations
@assert
@author
@backupGlobals
@backupStaticAttributes
@codeCoverageIgnore*
@covers
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. The XML Configuration File
PHPUnit
Test Suites
Groups
Including and Excluding Files for Code Coverage
Logging
Test Listeners
Setting PHP INI settings, Constants and Global Variables
Configuring Browsers for Selenium RC
D. Index
E. Bibliography
F. Copyright

Chapter 1. Automating Tests

Even good programmers make mistakes. The difference between a good programmer and a bad programmer is that the good programmer uses tests to detect his mistakes as soon as possible. The sooner you test for a mistake the greater your chance of finding it and the less it will cost to find and fix. This explains why leaving testing until just before releasing software is so problematic. Most errors do not get caught at all, and the cost of fixing the ones you do catch is so high that you have to perform triage with the errors because you just cannot afford to fix them all.

Testing with PHPUnit is not a totally different activity from what you should already be doing. It is just a different way of doing it. The difference is between testing, that is, checking that your program behaves as expected, and performing a battery of tests, runnable code-fragments that automatically test the correctness of parts (units) of the software. These runnable code-fragments are called unit tests.

In this chapter we will go from simple print-based testing code to a fully automated test. Imagine that we have been asked to test PHP's built-in array. One bit of functionality to test is the function count(). For a newly created array we expect the count() function to return 0. After we add an element, count() should return 1. Example 1.1 shows what we want to test.

Example 1.1: Testing array operations

<?php
$fixture = array();
// $fixture is expected to be empty.
 
$fixture[] = 'element';
// $fixture is expected to contain one element.
?>

A really simple way to check whether we are getting the results we expect is to print the result of count() before and after adding the element (see Example 1.2). If we get 0 and then 1, array and count() behave as expected.

Example 1.2: Using print to test array operations

<?php
$fixture = array();
print count($fixture) . "\n";
 
$fixture[] = 'element';
print count($fixture) . "\n";
?>
0
1

Now, we would like to move from tests that require manual interpretation to tests that can run automatically. In Example 1.3, we write the comparison of the expected and actual values into the test code and print ok if the values are equal. If we ever see a not ok message, we know something is wrong.

Example 1.3: Comparing expected and actual values to test array operations

<?php
$fixture = array();
print count($fixture) == 0 ? "ok\n" : "not ok\n";
 
$fixture[] = 'element';
print count($fixture) == 1 ? "ok\n" : "not ok\n";
?>
ok
ok

We now factor out the comparison of expected and actual values into a function that raises an Exception when there is a discrepancy (Example 1.4). This gives us two benefits: the writing of tests becomes easier and we only get output when something is wrong.

Example 1.4: Using an assertion function to test array operations

<?php
$fixture = array();
assertTrue(count($fixture) == 0);
 
$fixture[] = 'element';
assertTrue(count($fixture) == 1);
 
function assertTrue($condition)
{
    if (!$condition) {
        throw new Exception('Assertion failed.');
    }
}
?>

The test is now completely automated. Instead of just testing as we did with our first version, with this version we have an automated test.

The goal of using automated tests is to make fewer mistakes. While your code will still not be perfect, even with excellent tests, you will likely see a dramatic reduction in defects once you start automating tests. Automated tests give you justified confidence in your code. You can use this confidence to take more daring leaps in design (Refactoring), get along with your teammates better (Cross-Team Tests), improve relations with your customers, and go home every night with proof that the system is better now than it was this morning because of your efforts.

Chapter 2. PHPUnit's Goals

So far, we only have two tests for the array built-in and the count() function. When we start to test the numerous array_*() functions PHP offers, we will need to write a test for each of them. We could write the infrastructure for all these tests from scratch. However, it is much better to write a testing infrastructure once and then write only the unique parts of each test. PHPUnit is such an infrastructure.

A framework such as PHPUnit has to resolve a set of constraints, some of which seem always to conflict with each other. Simultaneously, tests should be:

Easy to learn to write.

If it's hard to learn how to write tests, developers will not learn to write them.

Easy to write.

If tests are not easy to write, developers will not write them.

Easy to read.

Test code should contain no extraneous overhead so that the test itself does not get lost in noise that surrounds it.

Easy to execute.

The tests should run at the touch of a button and present their results in a clear and unambiguous format.

Quick to execute.

Tests should run fast so so they can be run hundreds or thousands of times a day.

Isolated.

The tests should not affect each other. If the order in which the tests are run changes, the results of the tests should not change.

Composable.

We should be able to run any number or combination of tests together. This is a corollary of isolation.

There are two main clashes between these constraints:

Easy to learn to write versus easy to write.

Tests do not generally require all the flexibility of a programming language. Many testing tools provide their own scripting language that only includes the minimum necessary features for writing tests. The resulting tests are easy to read and write because they have no noise to distract you from the content of the tests. However, learning yet another programming language and set of programming tools is inconvenient and clutters the mind.

Isolated versus quick to execute.

If you want the results of one test to have no effect on the results of another test, each test should create the full state of the world before it begins to execute and return the world to its original state when it finishes. However, setting up the world can take a long time: for example connecting to a database and initializing it to a known state using realistic data.

PHPUnit attempts to resolve these conflicts by using PHP as the testing language. Sometimes the full power of PHP is overkill for writing little straight-line tests, but by using PHP we leverage all the experience and tools programmers already have in place. Since we are trying to convince reluctant testers, lowering the barrier to writing those initial tests is particularly important.

PHPUnit errs on the side of isolation over quick execution. Isolated tests are valuable because they provide high-quality feedback. You do not get a report with a bunch of test failures, which were really caused because one test at the beginning of the suite failed and left the world messed up for the rest of the tests. This orientation towards isolated tests encourages designs with a large number of simple objects. Each object can be tested quickly in isolation. The result is better designs and faster tests.

PHPUnit assumes that most tests succeed and it is not worth reporting the details of successful tests. When a test fails, that fact is worth noting and reporting. The vast majority of tests should succeed and are not worth commenting on except to count the number of tests that run. This is an assumption that is really built into the reporting classes, and not into the core of PHPUnit. When the results of a test run are reported, you see how many tests were executed, but you only see details for those that failed.

Tests are expected to be fine-grained, testing one aspect of one object. Hence, the first time a test fails, execution of the test halts, and PHPUnit reports the failure. It is an art to test by running in many small tests. Fine-grained tests improve the overall design of the system.

When you test an object with PHPUnit, you do so only through the object's public interface. Testing based only on publicly visible behaviour encourages you to confront and solve difficult design problems earlier, before the results of poor design can infect large parts of the system.

Chapter 3. Installing PHPUnit

PHPUnit should be installed using the PEAR Installer, the backbone of the PHP Extension and Application Repository that provides a distribution system for PHP packages.

Caution

Depending on your OS distribution and/or your PHP environment, you may need to install PEAR or update your existing PEAR installation before you can proceed with the instructions in this chapter.

sudo pear upgrade PEAR usually suffices to upgrade an existing PEAR installation. The PEAR Manual explains how to perform a fresh installation of PEAR.

Note

PHPUnit 3.6 requires PHP 5.2.7 (or later) but PHP 5.3.9 (or later) is highly recommended.

PHP_CodeCoverage, the library that is used by PHPUnit to collect and process code coverage information, depends on Xdebug 2.0.5 (or later) but Xdebug 2.1.3 (or later) is highly recommended.

The following two commands (which you may have to run as root) are all that is required to install PHPUnit using the PEAR Installer:

pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit

The following optional packages are available:

DbUnit

DbUnit port for PHP/PHPUnit to support database interaction testing.

This package can be installed using the following command:

pear install phpunit/DbUnit
PHPUnit_Selenium

Selenium RC integration for PHPUnit.

This package can be installed using the following command:

pear install phpunit/PHPUnit_Selenium
PHPUnit_Story

Story-based test runner for Behavior-Driven Development with PHPUnit.

This package can be installed using the following command:

pear install phpunit/PHPUnit_Story
PHPUnit_TestListener_DBUS

A TestListener that sends events to DBUS.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TestListener_DBUS
PHPUnit_TestListener_XHProf

A TestListener that uses XHProf for automated profiling of the tested code.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TestListener_XHProf
PHPUnit_TicketListener_Fogbugz

A ticket listener that interacts with the Fogbugz issue API.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TicketListener_Fogbugz
PHPUnit_TicketListener_GitHub

A ticket listener that interacts with the GitHub issue API.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TicketListener_GitHub
PHPUnit_TicketListener_GoogleCode

A ticket listener that interacts with the Google Code issue API.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TicketListener_GoogleCode
PHPUnit_TicketListener_Trac

A ticket listener that interacts with the Trac issue API.

This package can be installed using the following command:

pear install phpunit/PHPUnit_TicketListener_Trac
PHP_Invoker

A utility class for invoking callables with a timeout. This package is required to enforce test timeouts in strict mode.

This package can be installed using the following command:

pear install phpunit/PHP_Invoker

After the installation you can find the PHPUnit source files inside your local PEAR directory; the path is usually /usr/lib/php/PHPUnit.

Chapter 4. Writing Tests for PHPUnit

Example 4.1 shows how we can write tests using PHPUnit that exercise PHP's array operations. The example introduces the basic conventions and steps for writing tests with PHPUnit:

  1. The tests for a class Class go into a class ClassTest.

  2. ClassTest inherits (most of the time) from PHPUnit_Framework_TestCase.

  3. The tests are public methods that are named test*.

    Alternatively, you can use the @test annotation in a method's docblock to mark it as a test method.

  4. Inside the test methods, assertion methods such as assertEquals() (see the section called “Assertions”) are used to assert that an actual value matches an expected value.

Example 4.1: Testing array operations with PHPUnit

<?php
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        $this->assertEquals(0, count($stack));
 
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));
 
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}
?>

 

Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.

 
 --Martin Fowler

Test Dependencies

 

Unit Tests are primarily written as a good practice to help developers identify and fix bugs, to refactor code and to serve as documentation for a unit of software under test. To achieve these benefits, unit tests ideally should cover all the possible paths in a program. One unit test usually covers one specific path in one function or method. However a test method is not necessary an encapsulated, independent entity. Often there are implicit dependencies between test methods, hidden in the implementation scenario of a test.

 
 --Adrian Kuhn et. al.

PHPUnit supports the declaration of explicit dependencies between test methods. Such dependencies do not define the order in which the test methods are to be executed but they allow the returning of an instance of the test fixture by a producer and passing it to the dependent consumers.

  • A producer is a test method that yields its unit under test as return value.

  • A consumer is a test method that depends on one or more producers and their return values.

Example 4.2 shows how to use the @depends annotation to express dependencies between test methods.

Example 4.2: Using the @depends annotation to express dependencies

<?php
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testEmpty()
    {
        $stack = array();
        $this->assertEmpty($stack);
 
        return $stack;
    }
 
    /**
     * @depends testEmpty
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);
 
        return $stack;
    }
 
    /**
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}
?>

In the example above, the first test, testEmpty(), creates a new array and asserts that it is empty. The test then returns the fixture as its result. The second test, testPush(), depends on testEmpty() and is passed the result of that depended-upon test as its argument. Finally, testPop() depends upons testPush().

To quickly localize defects, we want our attention to be focussed on relevant failing tests. This is why PHPUnit skips the execution of a test when a depended-upon test has failed. This improves defect localization by exploiting the dependencies between tests as shown in Example 4.3.

Example 4.3: Exploiting the dependencies between tests

<?php
class DependencyFailureTest extends PHPUnit_Framework_TestCase
{
    public function testOne()
    {
        $this->assertTrue(FALSE);
    }
 
    /**
     * @depends testOne
     */
    public function testTwo()
    {
    }
}
?>
phpunit --verbose DependencyFailureTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FS

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) DependencyFailureTest::testOne
Failed asserting that false is true.

/home/sb/DependencyFailureTest.php:6

There was 1 skipped test:

1) DependencyFailureTest::testTwo
This test depends on "DependencyFailureTest::testOne" to pass.


FAILURES!
Tests: 1, Assertions: 1, Failures: 1, Skipped: 1.

A test may have more than one @depends annotation. PHPUnit does not change the order in which tests are executed, you have to ensure that the dependencies of a test can actually be met before the test is run.

Data Providers

A test method can accept arbitrary arguments. These arguments are to be provided by a data provider method (provider() in Example 4.4). The data provider method to be used is specified using the @dataProvider annotation.

A data provider method must be public and either return an array of arrays or an object that implements the Iterator interface and yields an array for each iteration step. For each array that is part of the collection the test method will be called with the contents of the array as its arguments.

Example 4.4: Using a data provider that returns an array of arrays

<?php
class DataTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provider
     */
    public function testAdd($a, $b, $c)
    {
        $this->assertEquals($c, $a + $b);
    }
 
    public function provider()
    {
        return array(
          array(0, 0, 0),
          array(0, 1, 1),
          array(1, 0, 1),
          array(1, 1, 3)
        );
    }
}
?>
phpunit DataTest
PHPUnit 3.6.0 by Sebastian Bergmann.

...F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) DataTest::testAdd with data set #3 (1, 1, 3)
Failed asserting that 2 matches expected 3.

/home/sb/DataTest.php:9

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

Example 4.5: Using a data provider that returns an Iterator object

<?php
require 'CsvFileIterator.php';
 
class DataTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provider
     */
    public function testAdd($a, $b, $c)
    {
        $this->assertEquals($c, $a + $b);
    }
 
    public function provider()
    {
        return new CsvFileIterator('data.csv');
    }
}
?>
phpunit DataTest
PHPUnit 3.6.0 by Sebastian Bergmann.

...F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) DataTest::testAdd with data set #3 ('1', '1', '3')
Failed asserting that 2 matches expected '3'.

/home/sb/DataTest.php:11

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

Example 4.6: The CsvFileIterator class

<?php
class CsvFileIterator implements Iterator {
    protected $file;
    protected $key = 0;
    protected $current;
 
    public function __construct($file) {
        $this->file = fopen($file, 'r');
    }
 
    public function __destruct() {
        fclose($this->file);
    }
 
    public function rewind() {
        rewind($this->file);
        $this->current = fgetcsv($this->file);
        $this->key = 0;
    }
 
    public function valid() {
        return !feof($this->file);
    }
 
    public function key() {
        return $this->key;
    }
 
    public function current() {
        return $this->current;
    }
 
    public function next() {
        $this->current = fgetcsv($this->file);
        $this->key++;
    }
}
?>

Note

When a test receives input from both a @dataProvider method and from one or more tests it @depends on, the arguments from the data provider will come before the ones from depended-upon tests.

Note

When a test depends on a test that uses data providers, the depending test will be executed when the test it depends upon is successful for at least one data set. The result of a test that uses data providers cannot be injected into a depending test.

Note

All data providers are executed before the first call to the setUp function. Because of that you can't access any variables you create there from within a data provider.

Testing Exceptions

Example 4.7 shows how to use the @expectedException annotation to test whether an exception is thrown inside the tested code.

Example 4.7: Using the @expectedException annotation

<?php
class ExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
    }
}
?>
phpunit ExceptionTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) ExceptionTest::testException
Expected exception InvalidArgumentException


FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Additionally, you can use @expectedExceptionMessage and @expectedExceptionCode in combination with @expectedException to test the exception message and exception code as shown in Example 4.8.

Example 4.8: Using the @expectedExceptionMessage and @expectedExceptionCode annotations

<?php
class ExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException        InvalidArgumentException
     * @expectedExceptionMessage Right Message
     */
    public function testExceptionHasRightMessage()
    {
        throw new InvalidArgumentException('Some Message', 10);
    }
 
    /**
     * @expectedException     InvalidArgumentException
     * @expectedExceptionCode 20
     */
    public function testExceptionHasRightCode()
    {
        throw new InvalidArgumentException('Some Message', 10);
    }
}
?>
phpunit ExceptionTest
PHPUnit 3.6.6 by Sebastian Bergmann.

FF

Time: 0 seconds, Memory: 3.00Mb

There were 2 failures:

1) ExceptionTest::testExceptionHasRightMessage
Failed asserting that exception message 'Some Message' contains 'Right Message'.


2) ExceptionTest::testExceptionHasRightCode
Failed asserting that expected exception code 20 is equal to 10.


FAILURES!
Tests: 2, Assertions: 4, Failures: 2.

Alternatively, you can use the setExpectedException() method to set the expected exception as shown in Example 4.9.

Example 4.9: Expecting an exception to be raised by the tested code

<?php
class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        $this->setExpectedException('InvalidArgumentException');
    }
 
    public function testExceptionHasRightMessage()
    {
        $this->setExpectedException(
          'InvalidArgumentException', 'Right Message'
        );
        throw new InvalidArgumentException('Some Message', 10);
    }
 
    public function testExceptionHasRightCode()
    {
        $this->setExpectedException(
          'InvalidArgumentException', 'Right Message', 20
        );
        throw new InvalidArgumentException('The Right Message', 10);
    }
}?>
phpunit ExceptionTest
PHPUnit 3.6.6 by Sebastian Bergmann.

FFF

Time: 0 seconds, Memory: 3.00Mb

There were 3 failures:

1) ExceptionTest::testException
Expected exception InvalidArgumentException


2) ExceptionTest::testExceptionHasRightMessage
Failed asserting that exception message 'Some Message' contains 'Right Message'.


3) ExceptionTest::testExceptionHasRightCode
Failed asserting that expected exception code 20 is equal to 10.


FAILURES!
Tests: 3, Assertions: 6, Failures: 3.

Table 4.1 shows the methods provided for testing exceptions.

Table 4.1. Methods for testing exceptions

MethodMeaning
void setExpectedException(string $exceptionName[, string $exceptionMessage = '', integer $exceptionCode = NULL])Set the expected $exceptionName, $exceptionMessage, and $exceptionCode.
String getExpectedException()Return the name of the expected exception.

You can also use the approach shown in Example 4.10 to test exceptions.

Example 4.10: Alternative approach to testing exceptions

<?php
class ExceptionTest extends PHPUnit_Framework_TestCase {
    public function testException() {
        try {
            // ... Code that is expected to raise an exception ...
        }
 
        catch (InvalidArgumentException $expected) {
            return;
        }
 
        $this->fail('An expected exception has not been raised.');
    }
}
?>

If the code that is expected to raise an exception in Example 4.10 does not raise the expected exception, the subsequent call to fail() will halt the test and signal a problem with the test. If the expected exception is raised, the catch block will be executed, and the test will end successfully.

Testing PHP Errors

By default, PHPUnit converts PHP errors, warnings, and notices that are triggered during the execution of a test to an exception. Using these exceptions, you can, for instance, expect a test to trigger a PHP error as shown in Example 4.11.

Example 4.11: Expecting a PHP error using @expectedException

<?php
class ExpectedErrorTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException PHPUnit_Framework_Error
     */
    public function testFailingInclude()
    {
        include 'not_existing_file.php';
    }
}
?>
phpunit ExpectedErrorTest
PHPUnit 3.6.0 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 5.25Mb

OK (1 test, 1 assertion)

PHPUnit_Framework_Error_Notice and PHPUnit_Framework_Error_Warning represent PHP notices and warnings, respectively.

Note

You should be as specific as possible when testing exceptions. Testing for classes that are too generic might lead to undesirable side-effects. Accordingly, testing for the Exception class with @expectedException or setExpectedException() is no longer permitted.

When testing that relies on php functions that trigger errors like fopen it can sometimes be useful to use error suppression while testing. This allows you to check the return values by suppressing notices that would lead to a phpunit PHPUnit_Framework_Error_Notice.

Example 4.12: Testing return values of code that uses PHP Errors

<?php
class ErrorSuppressionTest extends PHPUnit_Framework_TestCase
{
    public function testFileWriting() {
        $writer = new FileWriter;
        $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff'));
    }
}
class FileWriter
{
    public function write($file, $content) {
        $file = fopen($file, 'w');
        if($file == false) {
            return false;
        }
        // ...
    }
}
 
?>
phpunit ErrorSuppressionTest
PHPUnit 3.6.0 by Sebastian Bergmann.

.

Time: 1 seconds, Memory: 5.25Mb

OK (1 test, 1 assertion)


Without the error suppression the test would fail reporting fopen(/is-not-writeable/file): failed to open stream: No such file or directory.

Testing Output

Sometimes you want to assert that the execution of a method, for instance, generates an expected output (via echo or print, for example). The PHPUnit_Framework_TestCase class uses PHP's Output Buffering feature to provide the functionality that is necessary for this.

Example 4.13 shows how to use the expectOutputString() method to set the expected output. If this expected output is not generated, the test will be counted as a failure.

Example 4.13: Testing the output of a function or method

<?php
class OutputTest extends PHPUnit_Framework_TestCase
{
    public function testExpectFooActualFoo()
    {
        $this->expectOutputString('foo');
        print 'foo';
    }
 
    public function testExpectBarActualBaz()
    {
        $this->expectOutputString('bar');
        print 'baz';
    }
}
?>
phpunit OutputTest
PHPUnit 3.6.0 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) OutputTest::testExpectBarActualBaz
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'


FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Table 4.2 shows the methods provided for testing output

Table 4.2. Methods for testing output

MethodMeaning
void expectOutputRegex(string $regularExpression)Set up the expectation that the output matches a $regularExpression.
void expectOutputString(string $expectedString)Set up the expectation that the output is equal to an $expectedString.
bool setOutputCallback(callable $callback)Sets up a callback that is used to, for instance, normalize the actual output.

Note

Please note that PHPUnit swallows all output that is emitted during the execution of a test. In strict mode, a test that emits output will fail.

Assertions

This section lists the various assertion methods that are available.

assertArrayHasKey()

assertArrayHasKey(mixed $key, array $array[, string $message = ''])

Reports an error identified by $message if $array does not have the $key.

assertArrayNotHasKey() is the inverse of this assertion and takes the same arguments.

Example 4.14: Usage of assertArrayHasKey()

<?php
class ArrayHasKeyTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertArrayHasKey('foo', array('bar' => 'baz'));
    }
}
?>
phpunit ArrayHasKeyTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) ArrayHasKeyTest::testFailure
Failed asserting that an array has the key 'foo'.

/home/sb/ArrayHasKeyTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertClassHasAttribute()

assertClassHasAttribute(string $attributeName, string $className[, string $message = ''])

Reports an error identified by $message if $className::attributeName does not exist.

assertClassNotHasAttribute() is the inverse of this assertion and takes the same arguments.

Example 4.15: Usage of assertClassHasAttribute()

<?php
class ClassHasAttributeTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertClassHasAttribute('foo', 'stdClass');
    }
}
?>
phpunit ClassHasAttributeTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) ClassHasAttributeTest::testFailure
Failed asserting that class "stdClass" has attribute "foo".

/home/sb/ClassHasAttributeTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertClassHasStaticAttribute()

assertClassHasStaticAttribute(string $attributeName, string $className[, string $message = ''])

Reports an error identified by $message if $className::attributeName does not exist.

assertClassNotHasStaticAttribute() is the inverse of this assertion and takes the same arguments.

Example 4.16: Usage of assertClassHasStaticAttribute()

<?php
class ClassHasStaticAttributeTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertClassHasStaticAttribute('foo', 'stdClass');
    }
}
?>
phpunit ClassHasStaticAttributeTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) ClassHasStaticAttributeTest::testFailure
Failed asserting that class "stdClass" has static attribute "foo".

/home/sb/ClassHasStaticAttributeTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertContains()

assertContains(mixed $needle, Iterator|array $haystack[, string $message = ''])

Reports an error identified by $message if $needle is not an element of $haystack.

assertNotContains() is the inverse of this assertion and takes the same arguments.

assertAttributeContains() and assertAttributeNotContains() are convenience wrappers that use a public, protected, or private attribute of a class or object as the haystack.

Example 4.17: Usage of assertContains()

<?php
class ContainsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertContains(4, array(1, 2, 3));
    }
}
?>
phpunit ContainsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) ContainsTest::testFailure
Failed asserting that an array contains 4.

/home/sb/ContainsTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertContains(string $needle, string $haystack[, string $message = ''])

Reports an error identified by $message if $needle is not a substring of $haystack.

Example 4.18: Usage of assertContains()

<?php
class ContainsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertContains('baz', 'foobar');
    }
}
?>
phpunit ContainsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) ContainsTest::testFailure
Failed asserting that 'foobar' contains "baz".

/home/sb/ContainsTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertContainsOnly()

assertContainsOnly(string $type, Iterator|array $haystack[, boolean $isNativeType = NULL, string $message = ''])

Reports an error identified by $message if $haystack does not contain only variables of type $type.

$isNativeType is a flag used to indicate whether $type is a native PHP type or not.

assertNotContainsOnly() is the inverse of this assertion and takes the same arguments.

assertAttributeContainsOnly() and assertAttributeNotContainsOnly() are convenience wrappers that use a public, protected, or private attribute of a class or object as the actual value.

Example 4.19: Usage of assertContainsOnly()

<?php
class ContainsOnlyTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertContainsOnly('string', array('1', '2', 3));
    }
}
?>
phpunit ContainsOnlyTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) ContainsOnlyTest::testFailure
Failed asserting that Array (
    0 => '1'
    1 => '2'
    2 => 3
) contains only values of type "string".

/home/sb/ContainsOnlyTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertCount()

assertCount($expectedCount, $haystack[, string $message = ''])

Reports an error identified by $message if the number of elements in $haystack is not $expectedCount.

assertNotCount() is the inverse of this assertion and takes the same arguments.

Example 4.20: Usage of assertCount()

<?php
class CountTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertCount(0, array('foo'));
    }
}
?>
phpunit CountTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) CountTest::testFailure
Failed asserting that actual size 1 matches expected size 0.

/home/sb/CountTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertEmpty()

assertEmpty(mixed $actual[, string $message = ''])

Reports an error identified by $message if $actual is not empty.

assertNotEmpty() is the inverse of this assertion and takes the same arguments.

assertAttributeEmpty() and assertAttributeNotEmpty() are convenience wrappers that can be applied to a public, protected, or private attribute of a class or object.

Example 4.21: Usage of assertEmpty()

<?php
class EmptyTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertEmpty(array('foo'));
    }
}
?>
phpunit EmptyTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) EmptyTest::testFailure
Failed asserting that an array is empty.

/home/sb/EmptyTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertEqualXMLStructure()

assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement[, boolean $checkAttributes = FALSE, string $message = ''])

Reports an error identified by $message if the XML Structure of the DOMElement in $actualElement is not equal to the XML structure of the DOMElement in $expectedElement.

Example 4.22: Usage of assertEqualXMLStructure()

<?php
class EqualXMLStructureTest extends PHPUnit_Framework_TestCase
{
    public function testFailureWithDifferentNodeNames()
    {
        $expected = new DOMElement('foo');
        $actual = new DOMElement('bar');
 
        $this->assertEqualXMLStructure($expected, $actual);
    }
 
    public function testFailureWithDifferentNodeAttributes()
    {
        $expected = new DOMDocument;
        $expected->loadXML('<foo bar="true" />');
 
        $actual = new DOMDocument;
        $actual->loadXML('<foo/>');
 
        $this->assertEqualXMLStructure(
          $expected->firstChild, $actual->firstChild, TRUE
        );
    }
 
    public function testFailureWithDifferentChildrenCount()
    {
        $expected = new DOMDocument;
        $expected->loadXML('<foo><bar/><bar/><bar/></foo>');
 
        $actual = new DOMDocument;
        $actual->loadXML('<foo><bar/></foo>');
 
        $this->assertEqualXMLStructure(
          $expected->firstChild, $actual->firstChild
        );
    }
 
    public function testFailureWithDifferentChildren()
    {
        $expected = new DOMDocument;
        $expected->loadXML('<foo><bar/><bar/><bar/></foo>');
 
        $actual = new DOMDocument;
        $actual->loadXML('<foo><baz/><baz/><baz/></foo>');
 
        $this->assertEqualXMLStructure(
          $expected->firstChild, $actual->firstChild
        );
    }
}
?>
phpunit EqualXMLStructureTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FFFF

Time: 0 seconds, Memory: 5.75Mb

There were 4 failures:

1) EqualXMLStructureTest::testFailureWithDifferentNodeNames
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'foo'
+'bar'

/home/sb/EqualXMLStructureTest.php:9

2) EqualXMLStructureTest::testFailureWithDifferentNodeAttributes
Number of attributes on node "foo" does not match
Failed asserting that 0 matches expected 1.

/home/sb/EqualXMLStructureTest.php:22

3) EqualXMLStructureTest::testFailureWithDifferentChildrenCount
Number of child nodes of "foo" differs
Failed asserting that 1 matches expected 3.

/home/sb/EqualXMLStructureTest.php:35

4) EqualXMLStructureTest::testFailureWithDifferentChildren
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'

/home/sb/EqualXMLStructureTest.php:48

FAILURES!
Tests: 4, Assertions: 8, Failures: 4.

assertEquals()

assertEquals(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the two variables $expected and $actual are not equal.

assertNotEquals() is the inverse of this assertion and takes the same arguments.

assertAttributeEquals() and assertAttributeNotEquals() are convenience wrappers that use a public, protected, or private attribute of a class or object as the actual value.

Example 4.23: Usage of assertEquals()

<?php
class EqualsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertEquals(1, 0);
    }
 
    public function testFailure2()
    {
        $this->assertEquals('bar', 'baz');
    }
 
    public function testFailure3()
    {
        $this->assertEquals("foo\nbar\nbaz\n", "foo\nbah\nbaz\n");
    }
}
?>
phpunit EqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FFF

Time: 0 seconds, Memory: 5.25Mb

There were 3 failures:

1) EqualsTest::testFailure
Failed asserting that 0 matches expected 1.

/home/sb/EqualsTest.php:6

2) EqualsTest::testFailure2
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'

/home/sb/EqualsTest.php:11

3) EqualsTest::testFailure3
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
 'foo
-bar
+bah
 baz
 '

/home/sb/EqualsTest.php:16

FAILURES!
Tests: 3, Assertions: 3, Failures: 3.

More specialized comparisons are used for specific argument types for $expected and $actual, see below.

assertEquals(float $expected, float $actual[, string $message = '', float $delta = 0])

Reports an error identified by $message if the two floats $expected and $actual are not within $delta of each other.

Please read about comparing floating-point numbers to understand why $delta is neccessary.

Example 4.24: Usage of assertEquals() with floats

<?php
class EqualsTest extends PHPUnit_Framework_TestCase
{
    public function testSuccess()
    {
        $this->assertEquals(1.0, 1.1, '', 0.2);
    }
 
    public function testFailure()
    {
        $this->assertEquals(1.0, 1.1);
    }
}
?>
phpunit EqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) EqualsTest::testFailure
Failed asserting that 1.1 matches expected 1.0.

/home/sb/EqualsTest.php:11

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

assertEquals(DOMDocument $expected, DOMDocument $actual[, string $message = ''])

Reports an error identified by $message if the uncommented canonical form of the XML documents represented by the two DOMDocument objects $expected and $actual are not equal.

Example 4.25: Usage of assertEquals() with DOMDocument objects

<?php
class EqualsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $expected = new DOMDocument;
        $expected->loadXML('<foo><bar/></foo>');
 
        $actual = new DOMDocument;
        $actual->loadXML('<bar><foo/></bar>');
 
        $this->assertEquals($expected, $actual);
    }
}
?>
phpunit EqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) EqualsTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
 <?xml version="1.0"?>
-<foo>
-  <bar/>
-</foo>
+<bar>
+  <foo/>
+</bar>

/home/sb/EqualsTest.php:12

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertEquals(object $expected, object $actual[, string $message = ''])

Reports an error identified by $message if the two objects $expected and $actual do not have equal attribute values.

Example 4.26: Usage of assertEquals() with objects

<?php
class EqualsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $expected = new stdClass;
        $expected->foo = 'foo';
        $expected->bar = 'bar';
 
        $actual = new stdClass;
        $actual->foo = 'bar';
        $actual->baz = 'bar';
 
        $this->assertEquals($expected, $actual);
    }
}
?>
phpunit EqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) EqualsTest::testFailure
Failed asserting that two objects are equal.
--- Expected
+++ Actual
@@ @@
 stdClass Object (
-    'foo' => 'foo'
-    'bar' => 'bar'
+    'foo' => 'bar'
+    'baz' => 'bar'
 )

/home/sb/EqualsTest.php:14

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertEquals(array $expected, array $actual[, string $message = ''])

Reports an error identified by $message if the two arrays $expected and $actual are not equal.

Example 4.27: Usage of assertEquals() with arrays

<?php
class EqualsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertEquals(array('a', 'b', 'c'), array('a', 'c', 'd'));
    }
}
?>
phpunit EqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) EqualsTest::testFailure
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
     0 => 'a'
-    1 => 'b'
-    2 => 'c'
+    1 => 'c'
+    2 => 'd'
 )

/home/sb/EqualsTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertFalse()

assertFalse(bool $condition[, string $message = ''])

Reports an error identified by $message if $condition is TRUE.

Example 4.28: Usage of assertFalse()

<?php
class FalseTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertFalse(TRUE);
    }
}
?>
phpunit FalseTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) FalseTest::testFailure
Failed asserting that true is false.

/home/sb/FalseTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertFileEquals()

assertFileEquals(string $expected, string $actual[, string $message = ''])

Reports an error identified by $message if the file specified by $expected does not have the same contents as the file specified by $actual.

assertFileNotEquals() is the inverse of this assertion and takes the same arguments.

Example 4.29: Usage of assertFileEquals()

<?php
class FileEqualsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertFileEquals('/home/sb/expected', '/home/sb/actual');
    }
}
?>
phpunit FileEqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) FileEqualsTest::testFailure
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'expected
+'actual
 '

/home/sb/FileEqualsTest.php:6

FAILURES!
Tests: 1, Assertions: 3, Failures: 1.

assertFileExists()

assertFileExists(string $filename[, string $message = ''])

Reports an error identified by $message if the file specified by $filename does not exist.

assertFileNotExists() is the inverse of this assertion and takes the same arguments.

Example 4.30: Usage of assertFileExists()

<?php
class FileExistsTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertFileExists('/path/to/file');
    }
}
?>
phpunit FileExistsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) FileExistsTest::testFailure
Failed asserting that file "/path/to/file" exists.

/home/sb/FileExistsTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertGreaterThan()

assertGreaterThan(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the value of $actual is not greater than the value of $expected.

assertAttributeGreaterThan() is a convenience wrapper that uses a public, protected, or private attribute of a class or object as the actual value.

Example 4.31: Usage of assertGreaterThan()

<?php
class GreaterThanTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertGreaterThan(2, 1);
    }
}
?>
phpunit GreaterThanTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) GreaterThanTest::testFailure
Failed asserting that 1 is greater than 2.

/home/sb/GreaterThanTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertGreaterThanOrEqual()

assertGreaterThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the value of $actual is not greater than or equal to the value of $expected.

assertAttributeGreaterThanOrEqual() is a convenience wrapper that uses a public, protected, or private attribute of a class or object as the actual value.

Example 4.32: Usage of assertGreaterThanOrEqual()

<?php
class GreatThanOrEqualTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertGreaterThanOrEqual(2, 1);
    }
}
?>
phpunit GreaterThanOrEqualTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) GreatThanOrEqualTest::testFailure
Failed asserting that 1 is equal to 2 or is greater than 2.

/home/sb/GreaterThanOrEqualTest.php:6

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

assertInstanceOf()

assertInstanceOf($expected, $actual[, $message = ''])

Reports an error identified by $message if $actual is not an instance of $expected.

assertNotInstanceOf() is the inverse of this assertion and takes the same arguments.

assertAttributeInstanceOf() and assertAttributeNotInstanceOf() are convenience wrappers that can be applied to a public, protected, or private attribute of a class or object.

Example 4.33: Usage of assertInstanceOf()

<?php
class InstanceOfTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertInstanceOf('RuntimeException', new Exception);
    }
}
?>
phpunit InstanceOfTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) InstanceOfTest::testFailure
Failed asserting that Exception Object (...) is an instance of class "RuntimeException".

/home/sb/InstanceOfTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertInternalType()

assertInternalType($expected, $actual[, $message = ''])

Reports an error identified by $message if $actual is not of the $expected type.

assertNotInternalType() is the inverse of this assertion and takes the same arguments.

assertAttributeInternalType() and assertAttributeNotInternalType() are convenience wrappers that can be applied to a public, protected, or private attribute of a class or object.

Example 4.34: Usage of assertInternalType()

<?php
class InternalTypeTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertInternalType('string', 42);
    }
}
?>
phpunit InternalTypeTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) InternalTypeTest::testFailure
Failed asserting that 42 is of type "string".

/home/sb/InternalTypeTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertLessThan()

assertLessThan(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the value of $actual is not less than the value of $expected.

assertAttributeLessThan() is a convenience wrapper that uses a public, protected, or private attribute of a class or object as the actual value.

Example 4.35: Usage of assertLessThan()

<?php
class LessThanTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertLessThan(1, 2);
    }
}
?>
phpunit LessThanTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) LessThanTest::testFailure
Failed asserting that 2 is less than 1.

/home/sb/LessThanTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertLessThanOrEqual()

assertLessThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the value of $actual is not less than or equal to the value of $expected.

assertAttributeLessThanOrEqual() is a convenience wrapper that uses a public, protected, or private attribute of a class or object as the actual value.

Example 4.36: Usage of assertLessThanOrEqual()

<?php
class LessThanOrEqualTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertLessThanOrEqual(1, 2);
    }
}
?>
phpunit LessThanOrEqualTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) LessThanOrEqualTest::testFailure
Failed asserting that 2 is equal to 1 or is less than 1.

/home/sb/LessThanOrEqualTest.php:6

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

assertNull()

assertNull(mixed $variable[, string $message = ''])

Reports an error identified by $message if $variable is not NULL.

assertNotNull() is the inverse of this assertion and takes the same arguments.

Example 4.37: Usage of assertNull()

<?php
class NullTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertNull('foo');
    }
}
?>
phpunit NotNullTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) NullTest::testFailure
Failed asserting that 'foo' is null.

/home/sb/NotNullTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertObjectHasAttribute()

assertObjectHasAttribute(string $attributeName, object $object[, string $message = ''])

Reports an error identified by $message if $object->attributeName does not exist.

assertObjectNotHasAttribute() is the inverse of this assertion and takes the same arguments.

Example 4.38: Usage of assertObjectHasAttribute()

<?php
class ObjectHasAttributeTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertObjectHasAttribute('foo', new stdClass);
    }
}
?>
phpunit ObjectHasAttributeTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) ObjectHasAttributeTest::testFailure
Failed asserting that object of class "stdClass" has attribute "foo".

/home/sb/ObjectHasAttributeTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertRegExp()

assertRegExp(string $pattern, string $string[, string $message = ''])

Reports an error identified by $message if $string does not match the regular expression $pattern.

assertNotRegExp() is the inverse of this assertion and takes the same arguments.

Example 4.39: Usage of assertRegExp()

<?php
class RegExpTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertRegExp('/foo/', 'bar');
    }
}
?>
phpunit RegExpTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) RegExpTest::testFailure
Failed asserting that 'bar' matches PCRE pattern "/foo/".

/home/sb/RegExpTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertStringMatchesFormat()

assertStringMatchesFormat(string $format, string $string[, string $message = ''])

Reports an error identified by $message if the $string does not match the $format string.

assertStringNotMatchesFormat() is the inverse of this assertion and takes the same arguments.

Example 4.40: Usage of assertStringMatchesFormat()

<?php
class StringMatchesFormatTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertStringMatchesFormat('%i', 'foo');
    }
}
?>
phpunit StringMatchesFormatTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) StringMatchesFormatTest::testFailure
Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+$/s".

/home/sb/StringMatchesFormatTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

The format string may contain the following placeholders:

  • %e: Represents a directory separator, for example / on Linux.

  • %s: One or more of anything (character or white space) except the end of line character.

  • %S: Zero or more of anything (character or white space) except the end of line character.

  • %a: One or more of anything (character or white space) including the end of line character.

  • %A: Zero or more of anything (character or white space) including the end of line character.

  • %w: Zero or more white space characters.

  • %i: A signed integer value, for example +3142, -3142.

  • %d: An unsigned integer value, for example 123456.

  • %x: One or more hexadecimal character. That is, characters in the range 0-9, a-f, A-F.

  • %f: A floating point number, for example: 3.142, -3.142, 3.142E-10, 3.142e+10.

  • %c: A single character of any sort.

assertStringMatchesFormatFile()

assertStringMatchesFormatFile(string $formatFile, string $string[, string $message = ''])

Reports an error identified by $message if the $string does not match the contents of the $formatFile.

assertStringNotMatchesFormatFile() is the inverse of this assertion and takes the same arguments.

Example 4.41: Usage of assertStringMatchesFormatFile()

<?php
class StringMatchesFormatFileTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertStringMatchesFormatFile('/path/to/expected.txt', 'foo');
    }
}
?>
phpunit StringMatchesFormatFileTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) StringMatchesFormatFileTest::testFailure
Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+
$/s".

/home/sb/StringMatchesFormatFileTest.php:6

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

assertSame()

assertSame(mixed $expected, mixed $actual[, string $message = ''])

Reports an error identified by $message if the two variables $expected and $actual do not have the same type and value.

assertNotSame() is the inverse of this assertion and takes the same arguments.

assertAttributeSame() and assertAttributeNotSame() are convenience wrappers that use a public, protected, or private attribute of a class or object as the actual value.

Example 4.42: Usage of assertSame()

<?php
class SameTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertSame('2204', 2204);
    }
}
?>
phpunit SameTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) SameTest::testFailure
Failed asserting that 2204 is identical to '2204'.

/home/sb/SameTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertSame(object $expected, object $actual[, string $message = ''])

Reports an error identified by $message if the two variables $expected and $actual do not reference the same object.

Example 4.43: Usage of assertSame() with objects

<?php
class SameTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertSame(new stdClass, new stdClass);
    }
}
?>
phpunit SameTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 4.75Mb

There was 1 failure:

1) SameTest::testFailure
Failed asserting that two variables reference the same object.

/home/sb/SameTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertSelectCount()

assertSelectCount(array $selector, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])

Reports an error identified by $message if the CSS selector $selector does not match $count elements in the DOMNode $actual.

$count can be one of the following types:

  • boolean: Asserts for presence of elements matching the selector (TRUE) or absence of elements (FALSE).
  • integer: Asserts the count of elements.
  • array: Asserts that the count is in a range specified by using <, >, <=, and >= as keys.

Example 4.44: Usage of assertSelectCount()

<?php
class SelectCountTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->xml = new DomDocument;
        $this->xml->loadXML('<foo><bar/><bar/><bar/></foo>');
    }
 
    public function testAbsenceFailure()
    {
        $this->assertSelectCount('foo bar', FALSE, $this->xml);
    }
 
    public function testPresenceFailure()
    {
        $this->assertSelectCount('foo baz', TRUE, $this->xml);
    }
 
    public function testExactCountFailure()
    {
        $this->assertSelectCount('foo bar', 5, $this->xml);
    }
 
    public function testRangeFailure()
    {
        $this->assertSelectCount('foo bar', array('>'=>6, '<'=>8), $this->xml);
    }
}
?>
phpunit SelectCountTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FFFF

Time: 0 seconds, Memory: 5.50Mb

There were 4 failures:

1) SelectCountTest::testAbsenceFailure
Failed asserting that true is false.

/home/sb/SelectCountTest.php:12

2) SelectCountTest::testPresenceFailure
Failed asserting that false is true.

/home/sb/SelectCountTest.php:17

3) SelectCountTest::testExactCountFailure
Failed asserting that 3 matches expected 5.

/home/sb/SelectCountTest.php:22

4) SelectCountTest::testRangeFailure
Failed asserting that false is true.

/home/sb/SelectCountTest.php:27

FAILURES!
Tests: 4, Assertions: 4, Failures: 4.

assertSelectEquals()

assertSelectEquals(array $selector, string $content, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])

Reports an error identified by $message if the CSS selector $selector does not match $count elements in the DOMNode $actual with the value $content.

$count can be one of the following types:

  • boolean: Asserts for presence of elements matching the selector (TRUE) or absence of elements (FALSE).
  • integer: Asserts the count of elements.
  • array: Asserts that the count is in a range specified by using <, >, <=, and >= as keys.

Example 4.45: Usage of assertSelectEquals()

<?php
class SelectEqualsTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->xml = new DomDocument;
        $this->xml->loadXML('<foo><bar>Baz</bar><bar>Baz</bar></foo>');
    }
 
    public function testAbsenceFailure()
    {
        $this->assertSelectEquals('foo bar', 'Baz', FALSE, $this->xml);
    }
 
    public function testPresenceFailure()
    {
        $this->assertSelectEquals('foo bar', 'Bat', TRUE, $this->xml);
    }
 
    public function testExactCountFailure()
    {
        $this->assertSelectEquals('foo bar', 'Baz', 5, $this->xml);
    }
 
    public function testRangeFailure()
    {
        $this->assertSelectEquals('foo bar', 'Baz', array('>'=>6, '<'=>8), $this->xml);
    }
}
?>
phpunit SelectEqualsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FFFF

Time: 0 seconds, Memory: 5.50Mb

There were 4 failures:

1) SelectEqualsTest::testAbsenceFailure
Failed asserting that true is false.

/home/sb/SelectEqualsTest.php:12

2) SelectEqualsTest::testPresenceFailure
Failed asserting that false is true.

/home/sb/SelectEqualsTest.php:17

3) SelectEqualsTest::testExactCountFailure
Failed asserting that 2 matches expected 5.

/home/sb/SelectEqualsTest.php:22

4) SelectEqualsTest::testRangeFailure
Failed asserting that false is true.

/home/sb/SelectEqualsTest.php:27

FAILURES!
Tests: 4, Assertions: 4, Failures: 4.

assertSelectRegExp()

assertSelectRegExp(array $selector, string $pattern, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])

Reports an error identified by $message if the CSS selector $selector does not match $count elements in the DOMNode $actual with a value that matches $pattern.

$count can be one of the following types:

  • boolean: Asserts for presence of elements matching the selector (TRUE) or absence of elements (FALSE).
  • integer: Asserts the count of elements.
  • array: Asserts that the count is in a range specified by using <, >, <=, and >= as keys.

Example 4.46: Usage of assertSelectRegExp()

<?php
class SelectRegExpTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        $this->xml = new DomDocument;
        $this->xml->loadXML('<foo><bar>Baz</bar><bar>Baz</bar></foo>');
    }
 
    public function testAbsenceFailure()
    {
        $this->assertSelectRegExp('foo bar', '/Ba.*/', FALSE, $this->xml);
    }
 
    public function testPresenceFailure()
    {
        $this->assertSelectRegExp('foo bar', '/B[oe]z]/', TRUE, $this->xml);
    }
 
    public function testExactCountFailure()
    {
        $this->assertSelectRegExp('foo bar', '/Ba.*/', 5, $this->xml);
    }
 
    public function testRangeFailure()
    {
        $this->assertSelectRegExp('foo bar', '/Ba.*/', array('>'=>6, '<'=>8), $this->xml);
    }
}
?>
phpunit SelectRegExpTest
PHPUnit 3.6.0 by Sebastian Bergmann.

FFFF

Time: 0 seconds, Memory: 5.50Mb

There were 4 failures:

1) SelectRegExpTest::testAbsenceFailure
Failed asserting that true is false.

/home/sb/SelectRegExpTest.php:12

2) SelectRegExpTest::testPresenceFailure
Failed asserting that false is true.

/home/sb/SelectRegExpTest.php:17

3) SelectRegExpTest::testExactCountFailure
Failed asserting that 2 matches expected 5.

/home/sb/SelectRegExpTest.php:22

4) SelectRegExpTest::testRangeFailure
Failed asserting that false is true.

/home/sb/SelectRegExpTest.php:27

FAILURES!
Tests: 4, Assertions: 4, Failures: 4.

assertStringEndsWith()

assertStringEndsWith(string $suffix, string $string[, string $message = ''])

Reports an error identified by $message if the $string does not end with $suffix.

assertStringEndsNotWith() is the inverse of this assertion and takes the same arguments.

Example 4.47: Usage of assertStringEndsWith()

<?php
class StringEndsWithTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertStringEndsWith('suffix', 'foo');
    }
}
?>
phpunit StringEndsWithTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 1 second, Memory: 5.00Mb

There was 1 failure:

1) StringEndsWithTest::testFailure
Failed asserting that 'foo' ends with "suffix".

/home/sb/StringEndsWithTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertStringEqualsFile()

assertStringEqualsFile(string $expectedFile, string $actualString[, string $message = ''])

Reports an error identified by $message if the file specified by $expectedFile does not have $actualString as its contents.

assertStringNotEqualsFile() is the inverse of this assertion and takes the same arguments.

Example 4.48: Usage of assertStringEqualsFile()

<?php
class StringEqualsFileTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertStringEqualsFile('/home/sb/expected', 'actual');
    }
}
?>
phpunit StringEqualsFileTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) StringEqualsFileTest::testFailure
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'expected
-'
+'actual'

/home/sb/StringEqualsFileTest.php:6

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

assertStringStartsWith()

assertStringStartsWith(string $prefix, string $string[, string $message = ''])

Reports an error identified by $message if the $string does not start with $prefix.

assertStringStartsNotWith() is the inverse of this assertion and takes the same arguments.

Example 4.49: Usage of assertStringStartsWith()

<?php
class StringStartsWithTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertStringStartsWith('prefix', 'foo');
    }
}
?>
phpunit StringStartsWithTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) StringStartsWithTest::testFailure
Failed asserting that 'foo' starts with "prefix".

/home/sb/StringStartsWithTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertTag()

assertTag(array $matcher, string $actual[, string $message = '', boolean $isHtml = TRUE])

Reports an error identified by $message if $actual is not matched by the $matcher.

$matcher is an associative array that specifies the match criteria for the assertion:

  • id: The node with the given id attribute must match the corresponsing value.
  • tags: The node type must match the corresponding value.
  • attributes: The node's attributes must match the corresponsing values in the $attributes associative array.
  • content: The text content must match the given value.
  • parent: The node's parent must match the $parent associative array.
  • child: At least one of the node's immediate children must meet the criteria described by the $child associative array.
  • ancestor: At least one of the node's ancestors must meet the criteria described by the $ancestor associative array.
  • descendant: At least one of the node's descendants must meet the criteria described by the $descendant associative array.
  • children: Associative array for counting children of a node.
    • count: The number of matching children must be equal to this number.
    • less_than: The number of matching children must be less than this number.
    • greater_than: The number of matching children must be greater than this number.
    • only: Another associative array consisting of the keys to use to match on the children, and only matching children will be counted.

assertNotTag() is the inverse of this assertion and takes the same arguments.

Example 4.50: Usage of assertTag()

<?php
// Matcher that asserts that there is an element with an id="my_id".
$matcher = array('id' => 'my_id');
 
// Matcher that asserts that there is a "span" tag.
$matcher = array('tag' => 'span');
 
// Matcher that asserts that there is a "span" tag with the content
// "Hello World".
$matcher = array('tag' => 'span', 'content' => 'Hello World');
 
// Matcher that asserts that there is a "span" tag with content matching the
// regular expression pattern.
$matcher = array('tag' => 'span', 'content' => '/Try P(HP|ython)/');
 
// Matcher that asserts that there is a "span" with an "list" class attribute.
$matcher = array(
  'tag'        => 'span',
  'attributes' => array('class' => 'list')
);
 
// Matcher that asserts that there is a "span" inside of a "div".
$matcher = array(
  'tag'    => 'span',
  'parent' => array('tag' => 'div')
);
 
// Matcher that asserts that there is a "span" somewhere inside a "table".
$matcher = array(
  'tag'      => 'span',
  'ancestor' => array('tag' => 'table')
);
 
// Matcher that asserts that there is a "span" with at least one "em" child.
$matcher = array(
  'tag'   => 'span',
  'child' => array('tag' => 'em')
);
 
// Matcher that asserts that there is a "span" containing a (possibly nested)
// "strong" tag.
$matcher = array(
  'tag'        => 'span',
  'descendant' => array('tag' => 'strong')
);
 
// Matcher that asserts that there is a "span" containing 5-10 "em" tags as
// immediate children.
$matcher = array(
  'tag'      => 'span',
  'children' => array(
    'less_than'    => 11,
    'greater_than' => 4,
    'only'         => array('tag' => 'em')
  )
);
 
// Matcher that asserts that there is a "div", with an "ul" ancestor and a "li"
// parent (with class="enum"), and containing a "span" descendant that contains
// an element with id="my_test" and the text "Hello World".
$matcher = array(
  'tag'        => 'div',
  'ancestor'   => array('tag' => 'ul'),
  'parent'     => array(
    'tag'        => 'li',
    'attributes' => array('class' => 'enum')
  ),
  'descendant' => array(
    'tag'   => 'span',
    'child' => array(
      'id'      => 'my_test',
      'content' => 'Hello World'
    )
  )
);
 
// Use assertTag() to apply a $matcher to a piece of $html.
$this->assertTag($matcher, $html);
 
// Use assertTag() to apply a $matcher to a piece of $xml.
$this->assertTag($matcher, $xml, '', FALSE);
?>

assertThat()

More complex assertions can be formulated using the PHPUnit_Framework_Constraint classes. They can be evaluated using the assertThat() method. Example 4.51 shows how the logicalNot() and equalTo() constraints can be used to express the same assertion as assertNotEquals().

assertThat(mixed $value, PHPUnit_Framework_Constraint $constraint[, $message = ''])

Reports an error identified by $message if the $value does not match the $constraint.

Example 4.51: Usage of assertThat()

<?php
class BiscuitTest extends PHPUnit_Framework_TestCase
{
    public function testEquals()
    {
        $theBiscuit = new Biscuit('Ginger');
        $myBiscuit  = new Biscuit('Ginger');
 
        $this->assertThat(
          $theBiscuit,
          $this->logicalNot(
            $this->equalTo($myBiscuit)
          )
        );
    }
}
?>

Table 4.3 shows the available PHPUnit_Framework_Constraint classes.

Table 4.3. Constraints

ConstraintMeaning
PHPUnit_Framework_Constraint_Attribute attribute(PHPUnit_Framework_Constraint $constraint, $attributeName)Constraint that applies another constraint to an attribute of a class or an object.
PHPUnit_Framework_Constraint_IsAnything anything()Constraint that accepts any input value.
PHPUnit_Framework_Constraint_ArrayHasKey arrayHasKey(mixed $key)Constraint that asserts that the array it is evaluated for has a given key.
PHPUnit_Framework_Constraint_TraversableContains contains(mixed $value)Constraint that asserts that the array or object that implements the Iterator interface it is evaluated for contains a given value.
PHPUnit_Framework_Constraint_IsEqual equalTo($value, $delta = 0, $maxDepth = 10)Constraint that checks if one value is equal to another.
PHPUnit_Framework_Constraint_Attribute attributeEqualTo($attributeName, $value, $delta = 0, $maxDepth = 10)Constraint that checks if a value is equal to an attribute of a class or of an object.
PHPUnit_Framework_Constraint_FileExists fileExists()Constraint that checks if the file(name) that it is evaluated for exists.
PHPUnit_Framework_Constraint_GreaterThan greaterThan(mixed $value)Constraint that asserts that the value it is evaluated for is greater than a given value.
PHPUnit_Framework_Constraint_Or greaterThanOrEqual(mixed $value)Constraint that asserts that the value it is evaluated for is greater than or equal to a given value.
PHPUnit_Framework_Constraint_ClassHasAttribute classHasAttribute(string $attributeName)Constraint that asserts that the class it is evaluated for has a given attribute.
PHPUnit_Framework_Constraint_ClassHasStaticAttribute classHasStaticAttribute(string $attributeName)Constraint that asserts that the class it is evaluated for has a given static attribute.
PHPUnit_Framework_Constraint_ObjectHasAttribute hasAttribute(string $attributeName)Constraint that asserts that the object it is evaluated for has a given attribute.
PHPUnit_Framework_Constraint_IsIdentical identicalTo(mixed $value)Constraint that asserts that one value is identical to another.
PHPUnit_Framework_Constraint_IsFalse isFalse()Constraint that asserts that the value it is evaluated is FALSE.
PHPUnit_Framework_Constraint_IsInstanceOf isInstanceOf(string $className)Constraint that asserts that the object it is evaluated for is an instance of a given class.
PHPUnit_Framework_Constraint_IsNull isNull()Constraint that asserts that the value it is evaluated is NULL.
PHPUnit_Framework_Constraint_IsTrue isTrue()Constraint that asserts that the value it is evaluated is TRUE.
PHPUnit_Framework_Constraint_IsType isType(string $type)Constraint that asserts that the value it is evaluated for is of a specified type.
PHPUnit_Framework_Constraint_LessThan lessThan(mixed $value)Constraint that asserts that the value it is evaluated for is smaller than a given value.
PHPUnit_Framework_Constraint_Or lessThanOrEqual(mixed $value)Constraint that asserts that the value it is evaluated for is smaller than or equal to a given value.
logicalAnd()Logical AND.
logicalNot(PHPUnit_Framework_Constraint $constraint)Logical NOT.
logicalOr()Logical OR.
logicalXor()Logical XOR.
PHPUnit_Framework_Constraint_PCREMatch matchesRegularExpression(string $pattern)Constraint that asserts that the string it is evaluated for matches a regular expression.
PHPUnit_Framework_Constraint_StringContains stringContains(string $string, bool $case)Constraint that asserts that the string it is evaluated for contains a given string.
PHPUnit_Framework_Constraint_StringEndsWith stringEndsWith(string $suffix)Constraint that asserts that the string it is evaluated for ends with a given suffix.
PHPUnit_Framework_Constraint_StringStartsWith stringStartsWith(string $prefix)Constraint that asserts that the string it is evaluated for starts with a given prefix.

assertTrue()

assertTrue(bool $condition[, string $message = ''])

Reports an error identified by $message if $condition is FALSE.

Example 4.52: Usage of assertTrue()

<?php
class TrueTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertTrue(FALSE);
    }
}
?>
phpunit TrueTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) TrueTest::testFailure
Failed asserting that false is true.

/home/sb/TrueTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

assertXmlFileEqualsXmlFile()

assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile[, string $message = ''])

Reports an error identified by $message if the XML document in $actualFile is not equal to the XML document in $expectedFile.

assertXmlFileNotEqualsXmlFile() is the inverse of this assertion and takes the same arguments.

Example 4.53: Usage of assertXmlFileEqualsXmlFile()

<?php
class XmlFileEqualsXmlFileTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertXmlFileEqualsXmlFile(
          '/home/sb/expected.xml', '/home/sb/actual.xml');
    }
}
?>
phpunit XmlFileEqualsXmlFileTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) XmlFileEqualsXmlFileTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
 <?xml version="1.0"?>
 <foo>
-  <bar/>
+  <baz/>
 </foo>

/home/sb/XmlFileEqualsXmlFileTest.php:7

FAILURES!
Tests: 1, Assertions: 3, Failures: 1.

assertXmlStringEqualsXmlFile()

assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml[, string $message = ''])

Reports an error identified by $message if the XML document in $actualXml is not equal to the XML document in $expectedFile.

assertXmlStringNotEqualsXmlFile() is the inverse of this assertion and takes the same arguments.

Example 4.54: Usage of assertXmlStringEqualsXmlFile()

<?php
class XmlStringEqualsXmlFileTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertXmlStringEqualsXmlFile(
          '/home/sb/expected.xml', '<foo><baz/></foo>');
    }
}
?>
phpunit XmlStringEqualsXmlFileTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) XmlStringEqualsXmlFileTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
 <?xml version="1.0"?>
 <foo>
-  <bar/>
+  <baz/>
 </foo>

/home/sb/XmlStringEqualsXmlFileTest.php:7

FAILURES!
Tests: 1, Assertions: 2, Failures: 1.

assertXmlStringEqualsXmlString()

assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml[, string $message = ''])

Reports an error identified by $message if the XML document in $actualXml is not equal to the XML document in $expectedXml.

assertXmlStringNotEqualsXmlString() is the inverse of this assertion and takes the same arguments.

Example 4.55: Usage of assertXmlStringEqualsXmlString()

<?php
class XmlStringEqualsXmlStringTest extends PHPUnit_Framework_TestCase
{
    public function testFailure()
    {
        $this->assertXmlStringEqualsXmlString(
          '<foo><bar/></foo>', '<foo><baz/></foo>');
    }
}
?>
phpunit XmlStringEqualsXmlStringTest
PHPUnit 3.6.0 by Sebastian Bergmann.

F

Time: 0 seconds, Memory: 5.00Mb

There was 1 failure:

1) XmlStringEqualsXmlStringTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
 <?xml version="1.0"?>
 <foo>
-  <bar/>
+  <baz/>
 </foo>

/home/sb/XmlStringEqualsXmlStringTest.php:7

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Chapter 5. The Command-Line Test Runner

The PHPUnit command-line test runner can be invoked through the phpunit command. The following code shows how to run tests with the PHPUnit command-line test runner:

phpunit ArrayTest
PHPUnit 3.6.0 by Sebastian Bergmann.

..

Time: 0 seconds


OK (2 tests, 2 assertions)

For each test run, the PHPUnit command-line tool prints one character to indicate progress:

.

Printed when the test succeeds.

F

Printed when an assertion fails while running the test method.

E

Printed when an error occurs while running the test method.

S

Printed when the test has been skipped (see Chapter 9).

I

Printed when the test is marked as being incomplete or not yet implemented (see Chapter 9).

PHPUnit distinguishes between failures and errors. A failure is a violated PHPUnit assertion such as a failing assertEquals() call. An error is an unexpected exception or a PHP error. Sometimes this distinction proves useful since errors tend to be easier to fix than failures. If you have a big list of problems, it is best to tackle the errors first and see if you have any failures left when they are all fixed.

Command-Line switches

Let's take a look at the command-line test runner's switches in the following code:

phpunit --help
PHPUnit 3.6.0 by Sebastian Bergmann.

Usage: phpunit [switches] UnitTest [UnitTest.php]
       phpunit [switches] <directory>

  --log-junit <file>        Log test execution in JUnit XML format to file.
  --log-tap <file>          Log test execution in TAP format to file.
  --log-json <file>         Log test execution in JSON format.

  --coverage-clover <file>  Generate code coverage report in Clover XML format.
  --coverage-html <dir>     Generate code coverage report in HTML format.
  --coverage-php <file>     Serialize PHP_CodeCoverage object to file.
  --coverage-text=<file>    Generate code coverage report in text format.
                            Default to writing to the standard output.

  --testdox-html <file>     Write agile documentation in HTML format to file.
  --testdox-text <file>     Write agile documentation in Text format to file.

  --filter <pattern>        Filter which tests to run.
  --group ...               Only runs tests from the specified group(s).
  --exclude-group ...       Exclude tests from the specified group(s).
  --list-groups             List available test groups.

  --loader <loader>         TestSuiteLoader implementation to use.
  --printer <printer>       TestSuiteListener implementation to use.
  --repeat <times>          Runs the test(s) repeatedly.

  --tap                     Report test execution progress in TAP format.
  --testdox                 Report test execution progress in TestDox format.

  --colors                  Use colors in output.
  --stderr                  Write to STDERR instead of STDOUT.
  --stop-on-error           Stop execution upon first error.
  --stop-on-failure         Stop execution upon first error or failure.
  --stop-on-skipped         Stop execution upon first skipped test.
  --stop-on-incomplete      Stop execution upon first incomplete test.
  --strict                  Run tests in strict mode.
  -v|--verbose              Output more verbose information.
  --debug                   Display debbuging information during test execution.

  --process-isolation       Run each test in a separate PHP process.
  --no-globals-backup       Do not backup and restore $GLOBALS for each test.
  --static-backup           Backup and restore static attributes for each test.

  --bootstrap <file>        A "bootstrap" PHP file that is run before the tests.
  -c|--configuration <file> Read configuration from XML file.
  --no-configuration        Ignore default configuration file (phpunit.xml).
  --include-path <path(s)>  Prepend PHP's include_path with given path(s).
  -d key[=value]            Sets a php.ini value.

  -h|--help                 Prints this usage information.
  --version                 Prints the version and exits.

  --debug                   Output debugging information.
phpunit UnitTest

Runs the tests that are provided by the class UnitTest. This class is expected to be declared in the UnitTest.php sourcefile.

UnitTest must be either a class that inherits from PHPUnit_Framework_TestCase or a class that provides a public static suite() method which returns an PHPUnit_Framework_Test object, for example an instance of the PHPUnit_Framework_TestSuite class.

phpunit UnitTest UnitTest.php

Runs the tests that are provided by the class UnitTest. This class is expected to be declared in the specified sourcefile.

--log-junit

Generates a logfile in JUnit XML format for the tests run. See Chapter 18 for more details.

--log-tap

Generates a logfile using the Test Anything Protocol (TAP) format for the: tests run. See Chapter 18 for more details.

--log-json

Generates a logfile using the JSON format. See Chapter 18 for more details.

--coverage-html

Generates a code coverage report in HTML format. See Chapter 14 for more details.

Please note that this functionality is only available when the tokenizer and Xdebug extensions are installed.

--coverage-clover

Generates a logfile in XML format with the code coverage information for the tests run. See Chapter 18 for more details.

Please note that this functionality is only available when the tokenizer and Xdebug extensions are installed.

--coverage-php

Generates a serialized PHP_CodeCoverage object with the code coverage information.

Please note that this functionality is only available when the tokenizer and Xdebug extensions are installed.

--coverage-text

Generates a logfile or command-line output in human readable format with the code coverage information for the tests run. See Chapter 18 for more details.

Please note that this functionality is only available when the tokenizer and Xdebug extensions are installed.

--testdox-html and --testdox-text

Generates agile documentation in HTML or plain text format for the tests that are run. See Chapter 15 for more details.

--filter

Only runs tests whose name matches the given pattern. The pattern can be either the name of a single test or a regular expression that matches multiple test names.

--group

Only runs tests from the specified group(s). A test can be tagged as belonging to a group using the @group annotation.

The @author annotation is an alias for @group allowing to filter tests based on their authors.

--exclude-group

Exclude tests from the specified group(s). A test can be tagged as belonging to a group using the @group annotation.

--list-groups

List available test groups.

--loader

Specifies the PHPUnit_Runner_TestSuiteLoader implementation to use.

The standard test suite loader will look for the sourcefile in the current working directory and in each directory that is specified in PHP's include_path configuration directive. Following the PEAR Naming Conventions, a class name such as Project_Package_Class is mapped to the sourcefile name Project/Package/Class.php.

--printer

Specifies the result printer to use. The printer class must extend PHPUnit_Util_Printer and implement the PHPUnit_Framework_TestListener interface.

--repeat

Repeatedly runs the test(s) the specified number of times.

--tap

Reports the test progress using the Test Anything Protocol (TAP). See Chapter 18 for more details.

--testdox

Reports the test progress as agile documentation. See Chapter 15 for more details.

--colors

Use colors in output.

--stderr

Optionally print to STDERR instead of STDOUT.

--stop-on-error

Stop execution upon first error.

--stop-on-failure

Stop execution upon first error or failure.

--stop-on-skipped

Stop execution upon first skipped test.

--stop-on-incomplete

Stop execution upon first incomplete test.

--strict

Run tests in strict mode.

--verbose

Output more verbose information, for instance the names of tests that were incomplete or have been skipped.

--process-isolation

Run each test in a separate PHP process.

--no-globals-backup

Do not backup and restore $GLOBALS. See the section called “Global State” for more details.

--static-backup

Backup and restore static attributes of user-defined classes. See the section called “Global State” for more details.

--bootstrap

A "bootstrap" PHP file that is run before the tests.

--configuration, -c

Read configuration from XML file. See Appendix C for more details.

If phpunit.xml or phpunit.xml.dist (in that order) exist in the current working directory and --configuration is not used, the configuration will be automatically read from that file.

--no-configuration

Ignore phpunit.xml and phpunit.xml.dist from the current working directory.

--include-path

Prepend PHP's include_path with given path(s).

-d

Sets the value of the given PHP configuration option.

--debug

Output debug information such as the name of a test when its execution starts.

Chapter 6. Fixtures

One of the most time-consuming parts of writing tests is writing the code to set the world up in a known state and then return it to its original state when the test is complete. This known state is called the fixture of the test.

In Example 4.1, the fixture was simply the array that is stored in the $fixture variable. Most of the time, though, the fixture will be more complex than a simple array, and 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 fixture. This problem gets even worse when you write several tests with similar fixtures. Without some help from the testing framework, we would have to duplicate the code that sets up the fixture for each test we write.

PHPUnit supports sharing the setup code. Before a test method is run, a template method called setUp() is invoked. setUp() is where you create the objects against which you will test. Once the test method has finished running, whether it succeeded or failed, another template method called tearDown() is invoked. tearDown() is where you clean up the objects against which you tested.

In Example 4.2 we used the producer-consumer relationship between tests to share fixture. This is not always desired or even possible. Example 6.1 shows how we can write the tests of the StackTest in such a way that not the fixture itself is reused but the code that creates it. First we declare the instance variable, $stack, that we are going to use instead of a method-local variable. Then we put the creation of the array fixture into the setUp() method. Finally, we remove the redundant code from the test methods and use the newly introduced instance variable, $this->stack, instead of the method-local variable $stack with the assertEquals() assertion method.

Example 6.1: Using setUp() to create the stack fixture

<?php
class StackTest extends PHPUnit_Framework_TestCase
{
    protected $stack;
 
    protected function setUp()
    {
        $this->stack = array();
    }
 
    public function testEmpty()
    {
        $this->assertTrue(empty($this->stack));
    }
 
    public function testPush()
    {
        array_push($this->stack, 'foo');
        $this->assertEquals('foo', $this->stack[count($this->stack)-1]);
        $this->assertFalse(empty($this->stack));
    }
 
    public function testPop()
    {
        array_push($this->stack, 'foo');
        $this->assertEquals('foo', array_pop($this->stack));
        $this->assertTrue(empty($this->stack));
    }
}
?>

The setUp() and tearDown() template methods are run once for each test method (and on fresh instances) of the test case class.

In addition, the setUpBeforeClass() and tearDownAfterClass() template methods are called before the first test of the test case class is run and after the last test of the test case class is run, respectively.

The example below shows all template methods that are available in a test case class.

Example 6.2: Example showing all template methods available

<?php
class TemplateMethodsTest extends PHPUnit_Framework_TestCase
{
    public static function setUpBeforeClass()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    protected function setUp()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    protected function assertPreConditions()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    public function testOne()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
        $this->assertTrue(TRUE);
    }
 
    public function testTwo()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
        $this->assertTrue(FALSE);
    }
 
    protected function assertPostConditions()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    protected function tearDown()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    public static function tearDownAfterClass()
    {
        fwrite(STDOUT, __METHOD__ . "\n");
    }
 
    protected function onNotSuccessfulTest(Exception $e)
    {
        fwrite(STDOUT, __METHOD__ . "\n");
        throw $e;
    }
}
?>
phpunit TemplateMethodsTest
PHPUnit 3.6.0 by Sebastian Bergmann.

TemplateMethodsTest::setUpBeforeClass
TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testOne
TemplateMethodsTest::assertPostConditions
TemplateMethodsTest::tearDown
.TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testTwo
TemplateMethodsTest::tearDown
TemplateMethodsTest::onNotSuccessfulTest
FTemplateMethodsTest::tearDownAfterClass


Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) TemplateMethodsTest::testTwo
Failed asserting that <boolean:false> is true.
/home/sb/TemplateMethodsTest.php:30

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

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 like files or sockets in setUp(). If your setUp() just creates plain PHP objects, you can generally ignore tearDown(). However, if you create many objects in your setUp(), you might want to unset() the variables pointing to those objects in your tearDown() so they can be garbage collected. The garbage collection of test case objects is not predictable.

Variations

What happens when you have two tests with slightly different setups? There are two possibilities:

  • If the setUp() code differs only slightly, move the code that differs from the setUp() code to the test method.

  • If you really have a different setUp(), you need a different test case class. Name the class after the difference in the setup.

Sharing Fixture

There are few good reasons to share fixtures between tests, but in most cases the need to share a fixture between tests stems from an unresolved design problem.

A good example of a fixture that makes sense to share across several tests is a database connection: you log into the database once and reuse the database connection instead of creating a new connection for each test. This makes your tests run faster.

Example 6.3 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.

Example 6.3: Sharing fixture between the tests of a test suite

<?php
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected static $dbh;
 
    public static function setUpBeforeClass()
    {
        self::$dbh = new PDO('sqlite::memory:');
    }
 
    public static function tearDownAfterClass()
    {
        self::$dbh = NULL;
    }
}
?>

It cannot be emphasized enough that sharing fixtures between tests reduces the value of the tests. The underlying design problem is that objects are not loosely coupled. You will achieve better results solving the underlying design problem and then writing tests using stubs (see Chapter 10), than by creating dependencies between tests at runtime and ignoring the opportunity to improve your design.

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 $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.

By default, PHPUnit runs 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. Optionally, this isolation can be extended to static attributes of classes.

Note

The implementation of the backup and restore operations for static attributes of classes requires PHP 5.3 (or greater).

The implementation of the backup and restore operations for global variables and static attributes of classes uses serialize() and unserialize().

Objects of some classes that are provided by PHP itself, such as PDO for example, cannot be serialized and the backup operation will break when such an object is stored in the $GLOBALS array, for instance.

The @backupGlobals annotation that is discussed in the section called “@backupGlobals can be used to control the backup and restore operations for global variables. Alternatively, you can provide a blacklist of global variables that are to be excluded from the backup and restore operations like this

class MyTest extends PHPUnit_Framework_TestCase
{
    protected $backupGlobalsBlacklist = array('globalVariable');

    // ...
}

Note

Please note that setting the $backupGlobalsBlacklist attribute inside the setUp() method, for instance, has no effect.

The @backupStaticAttributes annotation that is discussed in the section called “@backupStaticAttributes can be used to control the backup and restore operations for static attributes. Alternatively, you can provide a blacklist of static attributes that are to be excluded from the backup and restore operations like this

class MyTest extends PHPUnit_Framework_TestCase
{
    protected $backupStaticAttributesBlacklist = array(
      'className' => array('attributeName')
    );

    // ...
}

Note

Please note that setting the $backupStaticAttributesBlacklist attribute inside the setUp() method, for instance, has no effect.

Chapter 7. Organizing Tests

One of the goals of PHPUnit (see Chapter 2) is that tests should be composable: we want to be able to run any number or combination of tests together, for instance all tests for the whole project, or the tests for all classes of a component that is part of the project, or just the tests for a single class.

PHPUnit supports different ways of organizing tests and composing them into a test suite. This chapter shows the most commonly used approaches.

Composing a Test Suite Using the Filesystem

Probably the easiest way to compose a test suite is to keep all test case source files in a test directory. PHPUnit can automatically discover and run the tests by recursively traversing the test directory.

Lets take a look at the test suite of the Object_Freezer library. Looking at this project's directory structure, we see that the test case classes in the Tests directory mirror the package and class structure of the System Under Test (SUT) in the Object directory:

Object                              Tests
|-- Freezer                         |-- Freezer
|   |-- HashGenerator               |   |-- HashGenerator
|   |   `-- NonRecursiveSHA1.php    |   |   `-- NonRecursiveSHA1Test.php
|   |-- HashGenerator.php           |   |
|   |-- IdGenerator                 |   |-- IdGenerator
|   |   `-- UUID.php                |   |   `-- UUIDTest.php
|   |-- IdGenerator.php             |   |
|   |-- LazyProxy.php               |   |
|   |-- Storage                     |   |-- Storage
|   |   `-- CouchDB.php             |   |   `-- CouchDB
|   |                               |   |       |-- WithLazyLoadTest.php
|   |                               |   |       `-- WithoutLazyLoadTest.php
|   |-- Storage.php                 |   |-- StorageTest.php
|   `-- Util.php                    |   `-- UtilTest.php
`-- Freezer.php                     `-- FreezerTest.php

To run all tests for the library we just need to point the PHPUnit command-line test runner to the test directory:

phpunit Tests
PHPUnit 3.6.0 by Sebastian Bergmann.

............................................................ 60 / 75
...............

Time: 0 seconds

OK (75 tests, 164 assertions)

Note

If you point the PHPUnit command-line test runner to a directory it will look for *Test.php files.

To run only the tests that are declared in the Object_FreezerTest test case class in Tests/FreezerTest.php we can use the following command:

phpunit Tests/FreezerTest
PHPUnit 3.6.0 by Sebastian Bergmann.

............................

Time: 0 seconds

OK (28 tests, 60 assertions)

For more fine-grained control of which tests to run we can use the --filter switch:

phpunit --filter testFreezingAnObjectWorks Tests
PHPUnit 3.6.0 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 2 assertions)

Note

A drawback of this approach is that we have no control over the order in which the test are run. This can lead to problems with regard to test dependencies, see the section called “Test Dependencies”. In the next section you will see how you can make the test execution order explicit by using the XML configuration file.

Composing a Test Suite Using XML Configuration

PHPUnit's XML configuration file (Appendix C) can also be used to compose a test suite. Example 7.1 shows a minimal example that will add all *Test classes that are found in *Test.php files when the Tests is recursively traversed.

Example 7.1: Composing a Test Suite Using XML Configuration

<phpunit>
  <testsuites>
    <testsuite name="Object_Freezer">
      <directory>Tests</directory>
    </testsuite>
  </testsuites>
</phpunit>

The order in which tests are executed can be made explicit:

Example 7.2: Composing a Test Suite Using XML Configuration

<phpunit>
  <testsuites>
    <testsuite name="Object_Freezer">
      <file>Tests/Freezer/HashGenerator/NonRecursiveSHA1Test.php</file>
      <file>Tests/Freezer/IdGenerator/UUIDTest.php</file>
      <file>Tests/Freezer/UtilTest.php</file>
      <file>Tests/FreezerTest.php</file>
      <file>Tests/Freezer/StorageTest.php</file>
      <file>Tests/Freezer/Storage/CouchDB/WithLazyLoadTest.php</file>
      <file>Tests/Freezer/Storage/CouchDB/WithoutLazyLoadTest.php</file>
    </testsuite>
  </testsuites>
</phpunit>

Chapter 8. Database Testing

Many beginner and intermediate unit testing examples in any programming language suggest that it is perfectly easy to test your application's logic with simple tests. For database-centric applications this is far away from the reality. Start using Wordpress, TYPO3 or Symfony with Doctrine or Propel, for example, and you will easily experience considerable problems with PHPUnit: just because the database is so tightly coupled to these libraries.

You probably know this scenario from your daily work and projects, where you want to put your fresh or experienced PHPUnit skills to work and get stuck by one of the following problems:

  1. The method you want to test executes a rather large JOIN operation and uses the data to calculate some important results.

  2. Your business logic performs a mix of SELECT, INSERT, UPDATE and DELETE statements.

  3. You need to setup test data in (possibly much) more than two tables to get reasonable initial data for the methods you want to test.

The DbUnit extension considerably simplifies the setup of a database for testing purposes and allows you to verify the contents of a database after performing a series of operations. It can be installed like this:

pear install phpunit/DbUnit

Supported Vendors for Database Testing

DbUnit currently supports MySQL, PostgreSQL, Oracle and SQLite. Through Zend Framework or Doctrine 2 integrations it has access to other database systems such as IBM DB2 or Microsoft SQL Server.

Difficulties in Database Testing

There is a good reason why all the examples on unit testing do not include interactions with the database: these kind of tests are both complex to setup and maintain. While testing against your database you need to take care of the following variables:

  • The database schema and tables

  • Inserting the rows required for the test into these tables

  • Verifying the state of the database after your test has run

  • Cleanup the database for each new test

Because many database APIs such as PDO, MySQLi or OCI8 are cumbersome to use and verbose in writing doing these steps manually is an absolute nightmare.

Test code should be as short and precise as possible for several reasons:

  • You do not want to modify considerable amount of test code for little changes in your production code.

  • You want to be able to read and understand the test code easily, even months after writing it.

Additionally you have to realize that the database is essentially a global input variable to your code. Two tests in your test suite could run against the same database, possibly reusing data multiple times. Failures in one test can easily affect the result of the following tests making your testing experience very difficult. The previously mentioned cleanup step is of major importance to solve the database is a global input problem.

DbUnit helps to simplify all these problems with database testing in an elegant way.

What PHPUnit cannot help you with is the fact that database tests are very slow compared to tests not using the database. Depending on how large the interactions with your database are your tests could run a considerable amount of time. However if you keep the amount of data used for each test small and try to test as much code using non-database tests you can easily get away in under a minute even for large test suites.

The Doctrine 2 project's test suite, for example, currently has a test suite of about 1000 tests where nearly half of them accesses the database and still runs in 15 seconds against a MySQL database on a standard desktop computer.

The four stages of a database test

In his book on xUnit Test Patterns Gerard Meszaros lists the four stages of a unit-test:

  1. Set up fixture

  2. Exercise System Under Test

  3. Verify outcome

  4. Teardown

What is a Fixture?

A fixture describes the initial state your application and database are in when you execute a test.

Testing the database requires you to hook into at at least the setup and teardown to clean-up and write the required fixture data into your tables. However the database extension has good reason to revert the four stages in a database test to resemble the following workflow that is executed for each single test:

1. Clean-Up Database

Since there is always a first test that runs against the database you do not know exactly if there is already data in the tables. PHPUnit will execute a TRUNCATE against all the tables you specified to reset their status to empty.

2. Set up fixture

PHPUnit will then iterate over all the fixture rows specified and insert them into their respective tables.

3–5. Run Test, Verify outcome and Teardown

After the database is reset and loaded with its initial state the actual test is executed by PHPUnit. This part of the test code does not require awareness of the Database Extension at all, you can go on and test whatever you like with your code.

In your test use a special assertion called assertDataSetsEqual() for verification purposes, however this is entirely optional. This feature will be explained in the section Database Assertions.

Configuration of a PHPUnit Database TestCase

Usually when using PHPUnit your testcases would extend the PHPUnit_Framework_TestCase class in the following way:

require_once "PHPUnit/Framework/TestCase.php";

class MyTest extends PHPUnit_Framework_TestCase
{
    public function testCalculate()
    {
        $this->assertEquals(2, 1 + 1);
    }
}

If you want to test code that works with the Database Extension the setup is a bit more complex and you have to extend a different abstract TestCase requiring you to implement two abstract methods getConnection() and getDataSet():

require_once "PHPUnit/Extensions/Database/TestCase.php";

class MyGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    /**
     * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
     */
    public function getConnection()
    {
        $pdo = new PDO('sqlite::memory:');
        return $this->createDefaultDBConnection($pdo, ':memory:');
    }

    /**
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    public function getDataSet()
    {
        return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-seed.xml');
    }
}

Implementing getConnection()

To allow the clean-up and fixture loading functionalities to work the PHPUnit Database Extension requires access to a database connection abstracted accross vendors through the PDO library. It is important to note that your application does not need to be based on PDO to use PHPUnit's database extension, the connection is merely used for the clean-up and fixture setup.

In the previous example we create an in-memory Sqlite connection and pass it to the createDefaultDBConnection method which wraps the PDO instance and the second parameter (the database-name) in a very simple abstraction layer for database connections of the type PHPUnit_Extensions_Database_DB_IDatabaseConnection.

The section Using the Database Connection explains the API of this interface and how you can make the best use of it.

Implementing getDataSet()

The getDataSet() method defines how the initial state of the database should look before each test is executed. The state of a database is abstracted through the concepts DataSet and DataTable both being represented by the interfaces PHPUnit_Extensions_Database_DataSet_IDataSet and PHPUnit_Extensions_Database_DataSet_IDataTable. The next section will describe in detail how these concepts work and what the benefits are for using them in database testing.

For the implementation we only need to know that the getDataSet() method is called once during setUp() to retrieve the fixture data-set and insert it into the database. In the example we are using a factory method createFlatXMLDataSet($filename) that represents a data-set through an XML representation.

What about the Database Schema (DDL)?

PHPUnit assumes that the database schema with all its tables, triggers, sequences and views is created before a test is run. This means you as developer have to make sure that the database is correctly setup before running the suite.

There are several means to achieve this pre-condition to database testing.

  1. If you are using a persistent database (not Sqlite Memory) you can easily setup the database once with tools such as phpMyAdmin for MySQL and re-use the database for every test-run.

  2. If you are using libraries such as Doctrine 2 or Propel you can use their APIs to create the database schema you need once before you run the tests. You can utilize PHPUnit's Bootstrap and Configuration capabilities to execute this code whenever your tests are run.

Tip: Use your own Abstract Database TestCase

From the previous implementation example you can easily see that getConnection() method is pretty static and could be re-used in different database test-cases. Additionally to keep performance of your tests good and database overhead low you can refactor the code a little bit to get a generic abstract test case for your application, which still allows you to specify a different data-fixture for each test case:

require_once "PHPUnit/Extensions/Database/TestCase.php";

abstract class MyApp_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
{
    // only instantiate pdo once for test clean-up/fixture load
    static private $pdo = null;

    // only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test
    private $conn = null;

    final public function getConnection()
    {
        if ($this->conn === null) {
            if (self::$pdo == null) {
                self::$pdo = new PDO('sqlite::memory:');
            }
            $this->conn = $this->createDefaultDBConnection(self::$pdo, ':memory:');
        }

        return $this->conn;
    }
}

This has the database connection hardcoded in the PDO connection though. PHPUnit has another awesome feature that could make this testcase even more generic. If you use the XML Configuration you could make the database connection configurable per test-run. First let's create a phpunit.xml file in our tests/ directory of the application that looks like:

<?xml version="1.0" encoding="UTF-8" ?>
<phpunit>
    <php>
        <var name="DB_DSN" value="mysql:dbname=myguestbook;host=localhost" />
        <var name="DB_USER" value="user" />
        <var name="DB_PASSWD" value="passwd" />
        <var name="DB_DBNAME" value="myguestbook" />
    </php>
</phpunit>

We can now modify our test-case to look like:

abstract class Generic_Tests_DatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
{
    // only instantiate pdo once for test clean-up/fixture load
    static private $pdo = null;

    // only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test
    private $conn = null;

    final public function getConnection()
    {
        if ($this->conn === null) {
            if (self::$pdo == null) {
                self::$pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD'] );
            }
            $this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
        }

        return $this->conn;
    }
}

We can now run the database test suite using different configurations from the command-line interface:

user@desktop> phpunit --configuration developer-a.xml MyTests/
user@desktop> phpunit --configuration developer-b.xml MyTests/

The possibility to run the database tests against different database targets easily is very important if you are developing on the development machine. If several developers run the database tests against the same database connection you can easily experience test-failures because of race-conditions.

Understanding DataSets and DataTables

A central concept of PHPUnit's Database Extension are DataSets and DataTables. You should try to understand this simple concept to master database testing with PHPUnit. The DataSet and DataTable are an abstraction layer around your database tables, rows and columns. A simple API hides the underlying database contents in an object structure, which can also be implemented by other non-database sources.

This abstraction is necessary to compare the actual contents of a database against the expected contents. Expectations can be represented as XML, YAML, CSV files or PHP array for example. The DataSet and DataTable interfaces enable the comparison of this conceptually different sources, emulating relational database storage in a semantically simliar approach.

A workflow for database assertions in your tests then consists of three simple steps:

  • Specify one or more tables in your database by table name (actual dataset)

  • Specify the expected dataset in your preferred format (YAML, XML, ..)

  • Assert that both dataset representations equal each other.

Assertions are not the only use-case for the DataSet and DataTable in PHPUnit's Database Extension. As shown in the previous section they also describe the initial contents of a database. You are forced to define a fixture dataset by the Database TestCase, which is then used to:

  • Delete all the rows from the tables specified in the dataset.

  • Write all the rows in the data-tables into the database.

Available Implementations

There are three different types of datasets/datatables:

  • File-Based DataSets and DataTables

  • Query-Based DataSet and DataTable

  • Filter and Composition DataSets and DataTables

The file-based datasets and tables are generally used for the initial fixture and to describe the expected state of the database.

Flat XML DataSet

The most common dataset is called Flat XML. It is a very simple xml format where a tag inside the root node <dataset> represents exactly one row in the database. The tags name equals the table to insert the row into and an attribute represents the column. An example for a simple guestbook application could look like this:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" />
    <guestbook id="2" content="I like it!" user="nancy" created="2010-04-26 12:14:20" />
</dataset>

This is obviously easy to write. Here <guestbook> is the table name where two rows are inserted into each with four columns id, content, user and created with their respective values.

However this simplicity comes at a cost.

From the previous example it isn't obvious how you would specify an empty table. You can insert a tag with no attributes with the name of the empty table. A flat xml file for an empty guestbook table would then look like:

<?xml version="1.0" ?>
<dataset>
    <guestbook />
</dataset>

The handling of NULL values with the flat xml dataset is tedious. A NULL value is different than an empty string value in almost any database (Oracle being an exception), something that is difficult to describe in the flat xml format. You can represent a NULL's value by omitting the attribute from the row specification. If our guestbook would allow anonymous entries represented by a NULL value in the user column, a hypothetical state of the guestbook table could look like:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" />
    <guestbook id="2" content="I like it!" created="2010-04-26 12:14:20" />
</dataset>

In this case the second entry is posted anonymously. However this leads to a serious problem with column recognition. During dataset equality assertions each dataset has to specify what columns a table holds. If an attribute is NULL for all the rows of a data-table, how would the Database Extension know that the column should be part of the table?

The flat xml dataset makes a crucial assumption now, defining that the attributes on the first defined row of a table define the columns of this table. In the previous example this would mean id, content, user and created are columns of the guestbook table. For the second row where user is not defined a NULL would be inserted into the database.

When the first guestbook entry is deleted from the dataset only id, content and created would be columns of the guestbook table, since user is not specified.

To use the Flat XML dataset effectively when NULL values are relevant the first row of each table must not contain any NULL value and only successive rows are allowed to omit attributes. This can be awkward, since the order of the rows is a relevant factor for database assertions.

In turn, if you specify only a subset of the table columns in the Flat XML dataset all the omitted values are set to their default values. This will lead to errors if one of the omitted columns is defined as NOT NULL DEFAULT NULL.

In conclusion I can only advise using the Flat XML datasets if you do not need NULL values.

You can create a flat xml dataset instance from within your Database TestCase by calling the createFlatXmlDataSet($filename) method:

class MyTestCase extends PHPUnit_Extensions_Database_TestCase
{
    public function getDataSet()
    {
        return $this->createFlatXmlDataSet('myFlatXmlFixture.xml');
    }
}

XML DataSet

There is another more structured XML dataset, which is a bit more verbose to write but avoids the NULL problems of the Flat XML dataset. Inside the root node <dataset> you can specify <table>, <column>, <row>, <value> and <null /> tags. An equivalent dataset to the previously defined Guestbook Flat XML looks like:

<?xml version="1.0" ?>
<dataset>
    <table name="guestbook">
        <column>id</column>
        <column>content</column>
        <column>user</column>
        <column>created</column>
        <row>
            <value>1</value>
            <value>Hello buddy!</value>
            <value>joe</value>
            <value>2010-04-24 17:15:23</value>
        </row>
        <row>
            <value>2</value>
            <value>I like it!</value>
            <null />
            <value>2010-04-26 12:14:20</value>
        </row>
    </table>
</dataset>

Any defined <table> has a name and requires a definition of all the columns with their names. It can contain zero or any positive number of nested <row> elements. Defining no <row> element means the table is empty. The <value> and <null /> tags have to be specified in the order of the previously given <column> elements. The <null /> tag obviously means that the value is NULL.

You can create a xml dataset instance from within your Database TestCase by calling the createXmlDataSet($filename) method:

class MyTestCase extends PHPUnit_Extensions_Database_TestCase
{
    public function getDataSet()
    {
        return $this->createXMLDataSet('myXmlFixture.xml');
    }
}

MySQL XML DataSet

This new XML format is specific to the MySQL database server. Support for it was added in PHPUnit 3.5. Files in this format can be generated using the mysqldump utility. Unlike CSV datasets, which mysqldump also supports, a single file in this XML format can contain data for multiple tables. You can create a file in this format by invoking mysqldump like so:

mysqldump --xml -t -u [username] --password=[password] [database] > /path/to/file.xml
        

This file can be used in your Database TestCase by calling the createMySQLXMLDataSet($filename) method:

class MyTestCase extends PHPUnit_Extensions_Database_TestCase
{
    public function getDataSet()
    {
        return $this->createMySQLXMLDataSet('/path/to/file.xml');
    }
}

YAML DataSet

New with PHPUnit 3.4 is the ability to specify a dataset in the popular YAML format. For this to work you have to install PHPUnit 3.4 from PEAR with its optional Symfony YAML dependency. You can then write a YAML dataset for the guestbook example:

guestbook:
  -
    id: 1
    content: "Hello buddy!"
    user: "joe"
    created: 2010-04-24 17:15:23
  -
    id: 2
    content: "I like it!"
    user:
    created: 2010-04-26 12:14:20

This is simple, convient AND it solves the NULL issue that the similar Flat XML dataset has. A NULL in YAML is just the column name without no value specified. An empty string is specified as column1: "".

The YAML Dataset has no factory method on the Database TestCase currently, so you have to instantiate it manually:

require_once "PHPUnit/Extensions/Database/DataSet/YamlDataSet.php";

class YamlGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    protected function getDataSet()
    {
        return new PHPUnit_Extensions_Database_DataSet_YamlDataSet(
            dirname(__FILE__)."/_files/guestbook.yml"
        );
    }
}

CSV DataSet

Another file-based dataset is based on CSV files. Each table of the dataset is represented as a single CSV file. For our guestbook example we would define a guestbook-table.csv file:

id;content;user;created
1;"Hello buddy!";"joe";"2010-04-24 17:15:23"
2;"I like it!""nancy";"2010-04-26 12:14:20"

While this is very convenient for editing with Excel or OpenOffice, you cannot specify NULL values with the CSV dataset. An empty column will lead to the database default empty value being inserted into the column.

You can create a CSV DataSet by calling:

require_once 'PHPUnit/Extensions/Database/DataSet/CsvDataSet.php';

class CsvGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    protected function getDataSet()
    {
        $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet();
        $dataSet->addTable('guestbook', dirname(__FILE__)."/_files/guestbook.csv");
        return $dataSet;
    }
}

Array DataSet

There is no Array based DataSet in PHPUnit's Database Extension (yet), but we can implement our own easily. Our guestbook example should look like:

class ArrayGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    protected function getDataSet()
    {
        return new MyApp_DbUnit_ArrayDataSet(array(
            'guestbook' => array(
                array('id' => 1, 'content' => 'Hello buddy!', 'user' => 'joe', 'created' => '2010-04-24 17:15:23'),
                array('id' => 2, 'content' => 'I like it!',   'user' => null,  'created' => '2010-04-26 12:14:20'),
            ),
        ));
    }
}

A PHP DataSet has obvious advantages over all the other file-based datasets:

  • PHP Arrays can obviously handle NULL values.

  • You won't need additional files for assertions and can specify them directly in the TestCase.

For this dataset like the Flat XML, CSV and YAML DataSets the keys of the first specified row define the table's column names, in the previous case this would be id, content, user and created.

The implementation for this Array DataSet is simple and straightforward:

require_once 'PHPUnit/Util/Filter.php';

require_once 'PHPUnit/Extensions/Database/DataSet/AbstractDataSet.php';
require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableIterator.php';
require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTable.php';
require_once 'PHPUnit/Extensions/Database/DataSet/DefaultTableMetaData.php';

PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');

class MyApp_DbUnit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet
{
    /**
     * @var array
     */
    protected $tables = array();

    /**
     * @param array $data
     */
    public function __construct(array $data)
    {
        foreach ($data AS $tableName => $rows) {
            $columns = array();
            if (isset($rows[0])) {
                $columns = array_keys($rows[0]);
            }

            $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns);
            $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData);

            foreach ($rows AS $row) {
                $table->addRow($row);
            }
            $this->tables[$tableName] = $table;
        }
    }

    protected function createIterator($reverse = FALSE)
    {
        return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse);
    }

    public function getTable($tableName)
    {
        if (!isset($this->tables[$tableName])) {
            throw new InvalidArgumentException("$tableName is not a table in the current database.");
        }

        return $this->tables[$tableName];
    }
}

Query (SQL) DataSet

For database assertions you do not only need the file-based datasets but also a Query/SQL based Dataset that contains the actual contents of the database. This is where the Query DataSet shines:

$ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$ds->addTable('guestbook');

Adding a table just by name is an implicit way to define the data-table with the following query:

$ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$ds->addTable('guestbook', 'SELECT * FROM guestbook');

You can make use of this by specifying arbitrary queries for your tables, for example restricting rows, column or adding ORDER BY clauses:

$ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$ds->addTable('guestbook', 'SELECT id, content FROM guestbook ORDER BY created DESC');

The section on Database Assertions will show some more details on how to make use of the Query DataSet.

Database (DB) Dataset

Accessing the Test Connection you can automatically create a DataSet that consists of all the tables with their content in the database specified as second parameter to the Connections Factory method.

You can either create a dataset for the complete database as shown in testGuestbook(), or restrict it to a set of specified table names with a whitelist as shown in testFilteredGuestbook() method.

class MySqlGuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    /**
     * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
     */
    public function getConnection()
    {
        $database = 'my_database';
        $pdo = new PDO('mysql:...', $user, $password);
        return $this->createDefaultDBConnection($pdo, $database);
    }

    public function testGuestbook()
    {
        $dataSet = $this->getConnection()->createDataSet();
        // ...
    }

    public function testFilteredGuestbook()
    {
        $tableNames = array('guestbook');
        $dataSet = $this->getConnection()->createDataSet($tableNames);
        // ...
    }
}

Replacement DataSet

I have been talking about NULL problems with the Flat XML and CSV DataSet, but there is a slightly complicated workaround to get both types of datasets working with NULLs.

The Replacement DataSet is a decorator for an existing dataset and allows you to replace values in any column of the dataset with another replacement value. To get our guestbook example working with NULL values we specify the file like:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" />
    <guestbook id="2" content="I like it!" user="##NULL##" created="2010-04-26 12:14:20" />
</dataset>

We then wrap the Flat XML DataSet into a Replacement DataSet:

require_once 'PHPUnit/Extensions/Database/DataSet/ReplacementDataSet.php';

class ReplacementTest extends PHPUnit_Extensions_Database_TestCase
{
    public function getDataSet()
    {
        $ds = $this->createFlatXmlDataSet('myFlatXmlFixture.xml');
        $rds = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($ds);
        $rds->addFullReplacement('##NULL##', null);
        return $rds;
    }
}

DataSet Filter

If you have a large fixture file you can use the DataSet Filter for white- and blacklisting of tables and columns that should be contained in a sub-dataset. This is especially handy in combination with the DB DataSet to filter the columns of the datasets.

class DataSetFilterTest extends PHPUnit_Extensions_Database_TestCase
{
    public function testIncludeFilteredGuestbook()
    {
        $tableNames = array('guestbook');
        $dataSet = $this->getConnection()->createDataSet();

        $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet);
        $filterDataSet->addIncludeTables(array('guestbook'));
        $filterDataSet->setIncludeColumnsForTable('guestbook', array('id', 'content'));
        // ..
    }

    public function testExcludeFilteredGuestbook()
    {
        $tableNames = array('guestbook');
        $dataSet = $this->getConnection()->createDataSet();

        $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet);
        $filterDataSet->addExcludeTables(array('foo', 'bar', 'baz')); // only keep the guestbook table!
        $filterDataSet->setExcludeColumnsForTable('guestbook', array('user', 'created));
        // ..
    }
}

NOTE You cannot use both exclude and include column filtering on the same table, only on different ones. Plus it is only possible to either white- or blacklist tables, not both of them.

Composite DataSet

The composite DataSet is very useful for aggregating several already existing datasets into a single dataset. When several datasets contain the same table the rows are appended in the specified order. For example if we have two datasets fixture1.xml:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" />
</dataset>

and fixture2.xml:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="2" content="I like it!" user="##NULL##" created="2010-04-26 12:14:20" />
</dataset>

Using the Composite DataSet we can aggregate both fixture files:

class CompositeTest extends PHPUnit_Extensions_Database_TestCase
{
    public function getDataSet()
    {
        $ds1 = $this->createFlatXmlDataSet('fixture1.xml');
        $ds2 = $this->createFlatXmlDataSet('fixture2.xml');

        $compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet();
        $compositeDs->addDataSet($ds1);
        $compositeDs->addDataSet($ds2);

        return $compositeDs;
    }
}

Beware of Foreign Keys

During Fixture SetUp PHPUnit's Database Extension inserts the rows into the database in the order they are specified in your fixture. If your database schema uses foreign keys this means you have to specify the tables in an order that does not cause foreign key constraints to fail.

Implementing your own DataSets/DataTables

To understand the internals of DataSets and DataTables, lets have a look at the interface of a DataSet. You can skip this part if you do not plan to implement your own DataSet or DataTable.

interface PHPUnit_Extensions_Database_DataSet_IDataSet extends IteratorAggregate
{
    public function getTableNames();
    public function getTableMetaData($tableName);
    public function getTable($tableName);
    public function assertEquals(PHPUnit_Extensions_Database_DataSet_IDataSet $other);

    public function getReverseIterator();
}

The public interface is used internally by the assertDataSetsEqual() assertion on the Database TestCase to check for dataset quality. From the IteratorAggregate interface the IDataSet inherits the getIterator() method to iterate over all tables of the dataset. The additional reverse iterator method is required to successfully truncate the tables in reverse of the specified order.

To understand the need for a reverse iterator think of a two tables (TableA and TableB) where TableB holds a foreign key on a column of TableA. If for fixture setup a row is inserted into TableA and then a dependant record into TableB, then it is obvious that for deleting all the tables contents the reverse order run you into trouble with foreign key constraints.

Depending on the implementation different approaches are taken to add table instances to a dataset. For example, tables are added internally during construction from the source file in all file-based datasets such as YamlDataSet, XmlDataSet or FlatXmlDataSet.

A table is also represented by the following interface:

interface PHPUnit_Extensions_Database_DataSet_ITable
{
    public function getTableMetaData();
    public function getRowCount();
    public function getValue($row, $column);
    public function getRow($row);
    public function assertEquals(PHPUnit_Extensions_Database_DataSet_ITable $other);
}

Except the getTableMetaData() method it is pretty self-explainatory. The methods are used are all required for the different assertions of the Database Extension that are explained in the next chapter. The getTableMetaData() method has to return an implementation of the PHPUnit_Extensions_Database_DataSet_ITableMetaData interface, which describes the structure of the table. It holds information on:

  • The table name

  • An array of column-names of the table, ordered by their appearance in the result-set.

  • An array of the primary-key columns.

This interface also has an assertion that checks if two instances of Table Metadata equal each other, which is used by the data-set equality assertion.

The Connection API

There are three interesting methods on the Connection interface which has to be returned from the getConnection() method on the Database TestCase:

interface PHPUnit_Extensions_Database_DB_IDatabaseConnection
{
    public function createDataSet(Array $tableNames = NULL);
    public function createQueryTable($resultName, $sql);
    public function getRowCount($tableName, $whereClause = NULL);

    // ...
}
  1. The createDataSet() method creates a Database (DB) DataSet as described in the DataSet implementations section.

    class ConnectionTest extends PHPUnit_Extensions_Database_TestCase
    {
        public function testCreateDataSet()
        {
            $tableNames = array('guestbook');
            $dataSet = $this->getConnection()->createDataSet();
        }
    }
    
  2. The createQueryTable() method can be used to create instances of a QueryTable, give them a result name and SQL query. This is a handy method when it comes to result/table assertions as will be shown in the next section on the Database Assertions API.

    class ConnectionTest extends PHPUnit_Extensions_Database_TestCase
    {
        public function testCreateQueryTable()
        {
            $tableNames = array('guestbook');
            $queryTable = $this->getConnection()->createQueryTable('guestbook', 'SELECT * FROM guestbook');
        }
    }
    
  3. The getRowCount() method is a convienent way to access the number of rows in a table, optionally filtered by an additional where clause. This can be used with a simple equality assertion:

    class ConnectionTest extends PHPUnit_Extensions_Database_TestCase
    {
        public function testGetRowCount()
        {
            $this->assertEquals(2, $this->getConnection()->getRowCount('guestbook'));
        }
    }
    

Database Assertions API

For a testing tool the Database Extension surely provides some assertions that you can use to verify the current state ot the database, tables and the row-count of tables. This section describes this functionality in detail:

Asserting the Row-Count of a Table

It is often helpful to check if a table contains a specific amount of rows. You can easily achieve this without additional glue code using the Connection API. Say we wanted to check that after insertion of a row into our guestbook we not only have the two initial entries that have accompanied us in all the previous example, but a third one:

class GuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    public function testAddEntry()
    {
        $this->assertEquals(2, $this->getConnection()->getRowCount('guestbook'), "Pre-Condition");

        $guestbook = new Guestbook();
        $guestbook->addEntry("suzy", "Hello world!");

        $this->assertEquals(3, $this->getConnection()->getRowCount('guestbook'), "Inserting failed");
    }
}

Asserting the State of a Table

The previous assertion is helpful, but we surely want to check the actual contents of the table to verify that all the values were written into the correct columns. This can be achieved by a table assertion.

For this we would define a Query Table instance which derives its content from a table name and SQL query and compare it to a File/Array Based Data Set:

class GuestbookTest extends PHPUnit_Extensions_Database_TestCase
{
    public function testAddEntry()
    {
        $guestbook = new Guestbook();
        $guestbook->addEntry("suzy", "Hello world!");

        $queryTable = $this->getConnection()->createQueryTable(
            'guestbook', 'SELECT * FROM guestbook'
        );
        $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml")
                              ->getTable("guestbook");
        $this->assertTablesEqual($expectedTable, $queryTable);
    }
}

Now we have to write the expectedBook.xml Flat XML file for this assertion:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" />
    <guestbook id="2" content="I like it!" user="nancy" created="2010-04-26 12:14:20" />
    <guestbook id="3" content="Hello world!" user="suzy" created="2010-05-01 21:47:08" />
</dataset>

This assertion would only pass on exactly one second of the universe though, on 2010–05–01 21:47:08. Dates pose a special problem to database testing and we can circumvent the failure by omitting the created column from the assertion.

The adjusted expectedBook.xml Flat XML file would probably have to look like the following to make the assertion pass:

<?xml version="1.0" ?>
<dataset>
    <guestbook id="1" content="Hello buddy!" user="joe" />
    <guestbook id="2" content="I like it!" user="nancy" />
    <guestbook id="3" content="Hello world!" user="suzy" />
</dataset>

We have to fix up the Query Table call:

$queryTable = $this->getConnection()->createQueryTable(
    'guestbook', 'SELECT id, content, user FROM guestbook'
);

Asserting the Result of a Query

You can also assert the result of complex queries with the Query Table approach, just specify a a result name with a query and compare it to a dataset:

class ComplexQueryTest extends PHPUnit_Extensions_Database_TestCase
{
    public function testComplexQuery()
    {
        $queryTable = $this->getConnection()->createQueryTable(
            'myComplexQuery', 'SELECT complexQuery...'
        );
        $expectedTable = $this->createFlatXmlDataSet("complexQueryAssertion.xml")
                              ->getTable("myComplexQuery");
        $this->assertTablesEqual($expectedTable, $queryTable);
    }
}

Asserting the State of Multiple Tables

For sure you can assert the state of multiple tables at once and compare a query dataset against a file based dataset. There are two different ways for DataSet assertions.

  1. You can use the Database (DB) DataSet from the Connection and compare it to a File-Based DataSet.

    class DataSetAssertionsTest extends PHPUnit_Extensions_Database_TestCase
    {
        public function testCreateDataSetAssertion()
        {
            $dataSet = $this->getConnection()->createDataSet(array('guestbook'));
            $expectedDataSet = $this->createFlatXmlDataSet('guestbook.xml');
            $this->assertDataSetsEqual($expectedDataSet, $dataSet);
        }
    }
    
  2. You can construct the DataSet on your own:

    class DataSetAssertionsTest extends PHPUnit_Extensions_Database_TestCase
    {
        public function testManualDataSetAssertion()
        {
            $dataSet = new PHPUnit_Extensions_Database_DataSet_QueryDataSet();
            $dataSet->addTable('guestbook', 'SELECT id, content, user FROM guestbook'); // additional tables
            $expectedDataSet = $this->createFlatXmlDataSet('guestbook.xml');
    
            $this->assertDataSetsEqual($expectedDataSet, $dataSet);
        }
    }
    

Frequently Asked Questions

Will PHPUnit (re-)create the database schema for each test?

No, PHPUnit requires all database objects to be available when the suite is started. The Database, tables, sequences, triggers and views have to be created before you run the test suite.

Doctrine 2 or eZ Components have powerful tools that allows you to create the database schema from pre-defined datastructures, however these have to be hooked into the PHPUnit extension to allow automatic database re-creation before the complete test-suite is run.

Since each test completely cleans the database you are not even required to re-create the database for each test-run. A permanently available database works perfectly.

Am I required to use PDO in my application for the Database Extension to work?

No, PDO is only required for the fixture clean- and set-up and for assertions. You can use whatever database abstraction you want inside your own code.

What can I do, when I get a Too much Connections Error?

If you do not cache the PDO instance that is created from the TestCase getConnection() method the number of connections to the database is increasing by one or more with each database test. With default configuration MySql only allows 100 concurrent connections other vendors also have maximum connection limits.

The SubSection Use your own Abstract Database TestCase shows how you can prevent this error from happening by using a single cached PDO instance in all your tests.

How to handle NULL with Flat XML / CSV Datasets?

do not. For this you should use either the XML or the YAML DataSets.

Chapter 9. Incomplete and Skipped Tests

Incomplete Tests

When you are working on a new test case class, you might want to begin by writing empty test methods such as:

public function testSomething()
{
}

to keep track of the tests that you have to write. The problem with empty test methods is that they are interpreted as a success by the PHPUnit framework. This misinterpretation leads to the test reports being useless -- you cannot see whether a test is actually successful or just not yet implemented. Calling $this->fail() in the unimplemented test method does not help either, since then the test will be interpreted as a failure. This would be just as wrong as interpreting an unimplemented test as a success.

If we think of a successful test as a green light and a test failure as a red light, we need an additional yellow light to mark a test as being incomplete or not yet implemented. PHPUnit_Framework_IncompleteTest is a marker interface for marking an exception that is raised by a test method as the result of the test being incomplete or currently not implemented. PHPUnit_Framework_IncompleteTestError is the standard implementation of this interface.

Example 9.1 shows a test case class, SampleTest, that contains one test method, testSomething(). By calling the convenience method markTestIncomplete() (which automatically raises an PHPUnit_Framework_IncompleteTestError exception) in the test method, we mark the test as being incomplete.

Example 9.1: Marking a test as incomplete

<?php
class SampleTest extends PHPUnit_Framework_TestCase
{
    public function testSomething()
    {
        // Optional: Test anything here, if you want.
        $this->assertTrue(TRUE, 'This should already work.');
 
        // Stop here and mark this test as incomplete.
        $this->markTestIncomplete(
          'This test has not been implemented yet.'
        );
    }
}
?>

An incomplete test is denoted by an I in the output of the PHPUnit command-line test runner, as shown in the following example:

phpunit --verbose SampleTest
PHPUnit 3.6.0 by Sebastian Bergmann.

I

Time: 0 seconds, Memory: 3.75Mb

There was 1 incomplete test:

1) SampleTest::testSomething
This test has not been implemented yet.

/home/sb/SampleTest.php:12
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 1, Incomplete: 1.

Table 9.1 shows the API for marking tests as incomplete.

Table 9.1. API for Incomplete Tests

MethodMeaning
void markTestIncomplete()Marks the current test as incomplete.
void markTestIncomplete(string $message)Marks the current test as incomplete using $message as an explanatory message.

Skipping Tests

Not all tests can be run in every environment. Consider, for instance, a database abstraction layer that has several drivers for the different database systems it supports. The tests for the MySQL driver can of course only be run if a MySQL server is available.

Example 9.2 shows a test case class, DatabaseTest, that contains one test method, testConnection(). In the test case class' setUp() template method we check whether the MySQLi extension is available and use the markTestSkipped() method to skip the test if it is not.

Example 9.2: Skipping a test

<?php
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        if (!extension_loaded('mysqli')) {
            $this->markTestSkipped(
              'The MySQLi extension is not available.'
            );
        }
    }
 
    public function testConnection()
    {
        // ...
    }
}
?>

A test that has been skipped is denoted by an S in the output of the PHPUnit command-line test runner, as shown in the following example:

phpunit --verbose DatabaseTest
PHPUnit 3.6.0 by Sebastian Bergmann.

S

Time: 0 seconds, Memory: 3.75Mb

There was 1 skipped test:

1) DatabaseTest::testConnection
The MySQLi extension is not available.

/home/sb/DatabaseTest.php:9
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 0, Skipped: 1.

Table 9.2 shows the API for skipping tests.

Table 9.2. API for Skipping Tests

MethodMeaning
void markTestSkipped()Marks the current test as skipped.
void markTestSkipped(string $message)Marks the current test as skipped using $message as an explanatory message.

Chapter 10. Test Doubles

Gerard Meszaros introduces the concept of Test Doubles in [Meszaros2007] like this:

 

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. This could be because they aren't available, they will not return the results needed for the test or because executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behavior of the SUT.

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!

 
 --Gerard Meszaros

The getMock($className) method 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 class. This test double object can be used in every context where an object of the original class is expected.

By default, all methods of the original class are replaced with a dummy implementation that just returns NULL (without calling the original method). Using the will($this->returnValue()) method, for instance, you can configure these dummy implementations to return a value when called.

Limitations

Please 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.

Stubs

The practice of replacing an object with a test double that (optionally) returns configured return values is refered to as stubbing. You can use a stub to "replace a real component on which the SUT depends so that the test has a control point for the indirect inputs of the SUT. This allows the test to force the SUT down paths it might not otherwise execute".

Example 10.2 shows how to stub method calls and set up return values. We first use the getMock() method that is provided by the PHPUnit_Framework_TestCase class to set up a stub object that looks like an object of SomeClass (Example 10.1). We then use the Fluent Interface that 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.

Example 10.1: The class we want to stub

<?php
class SomeClass
{
    public function doSomething()
    {
        // Do something.
    }
}
?>

Example 10.2: Stubbing a method call to return a fixed value

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValue('foo'));
 
        // Calling $stub->doSomething() will now return
        // 'foo'.
        $this->assertEquals('foo', $stub->doSomething());
    }
}
?>

"Behind the scenes", PHPUnit automatically generates a new PHP class that implements the desired behavior when the getMock() method is used. The generated test double class can be configured through the optional arguments of the getMock() method.

  • By default, all methods of the given class are replaced with a test double that just returns NULL unless a return value is configured using will($this->returnValue()), for instance.

  • When the second (optional) parameter is provided, only the methods whose names are in the array are replaced with a configurable test double. The behavior of the other methods is not changed.

  • The third (optional) parameter may hold a parameter array that is passed to the original class' constructor (which is not replaced with a dummy implementation by default).

  • The fourth (optional) parameter can be used to specify a class name for the generated test double class.

  • The fifth (optional) parameter can be used to disable the call to the original class' constructor.

  • The sixth (optional) parameter can be used to disable the call to the original class' clone constructor.

  • The seventh (optional) parameter can be used to disable __autoload() during the generation of the test double class.

Alternatively, the Mock Builder API can be used to configure the generated test double class. Example 10.3 shows an example. Here's a list of the methods that can be used with the Mock Builder's fluent interface:

  • setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed.

  • setConstructorArgs(array $args) can be called to provide a parameter array that is passed to the original class' constructor (which is not replaced with a dummy implementation by default).

  • setMockClassName($name) can be used to specify a class name for the generated test double class.

  • disableOriginalConstructor() can be used to disable the call to the original class' constructor.

  • disableOriginalClone() can be used to disable the call to the original class' clone constructor.

  • disableAutoload() can be used to disable __autoload() during the generation of the test double class.

Example 10.3: Using the Mock Builder API can be used to configure the generated test double class

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMockBuilder('SomeClass')
                     ->disableOriginalConstructor()
                     ->getMock();
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValue('foo'));
 
        // Calling $stub->doSomething() will now return
        // 'foo'.
        $this->assertEquals('foo', $stub->doSomething());
    }
}
?>

Sometimes you want to return one of the arguments of a method call (unchanged) as the result of a stubbed method call. Example 10.4 shows how you can achieve this using returnArgument() instead of returnValue().

Example 10.4: Stubbing a method call to return one of the arguments

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnArgumentStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnArgument(0));
 
        // $stub->doSomething('foo') returns 'foo'
        $this->assertEquals('foo', $stub->doSomething('foo'));
 
        // $stub->doSomething('bar') returns 'bar'
        $this->assertEquals('bar', $stub->doSomething('bar'));
    }
}
?>

When testing a fluent interface, it is sometimes useful to have a stubbed method return a reference to the stubbed object. Example 10.5 shows how you can use returnSelf() to achiveve this.

Example 10.5: Stubbing a method call to return a reference to the stub object

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnSelf()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnSelf());
 
        // $stub->doSomething() returns $stub
        $this->assertSame($stub, $stub->doSomething());
    }
}
?>

Sometimes a stubbed method should return different values depending on a predefined list of arguments. You can use returnValueMap() to create a map that associates arguments with corresponding return values. See Example 10.6 for an example.

Example 10.6: Stubbing a method call to return the value from a map

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnValueMapStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Create a map of arguments to return values.
        $map = array(
          array('a', 'b', 'c', 'd'),
          array('e', 'f', 'g', 'h')
        );
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnValueMap($map));
 
        // $stub->doSomething() returns different values depending on
        // the provided arguments.
        $this->assertEquals('d', $stub->doSomething('a', 'b', 'c'));
        $this->assertEquals('h', $stub->doSomething('e', 'f', 'g'));
    }
}
?>

When the stubbed method call should return a calculated value instead of a fixed one (see returnValue()) or an (unchanged) argument (see returnArgument()), you can use returnCallback() to have the stubbed method return the result of a callback function or method. See Example 10.7 for an example.

Example 10.7: Stubbing a method call to return a value from a callback

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testReturnCallbackStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->returnCallback('str_rot13'));
 
        // $stub->doSomething($argument) returns str_rot13($argument)
        $this->assertEquals('fbzrguvat', $stub->doSomething('something'));
    }
}
?>

A simpler alternative to setting up a callback method may be to specify a list of desired return values. You can do this with the onConsecutiveCalls() method. See Example 10.8 for an example.

Example 10.8: Stubbing a method call to return a list of values in the specified order

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testOnConsecutiveCallsStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->onConsecutiveCalls(2, 3, 5, 7));
 
        // $stub->doSomething() returns a different value each time
        $this->assertEquals(2, $stub->doSomething());
        $this->assertEquals(3, $stub->doSomething());
        $this->assertEquals(5, $stub->doSomething());
    }
}
?>

Instead of returning a value, a stubbed method can also raise an exception. Example 10.9 shows how to use throwException() to do this.

Example 10.9: Stubbing a method call to throw an exception

<?php
require_once 'SomeClass.php';
 
class StubTest extends PHPUnit_Framework_TestCase
{
    public function testThrowExceptionStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMock('SomeClass');
 
        // Configure the stub.
        $stub->expects($this->any())
             ->method('doSomething')
             ->will($this->throwException(new Exception));
 
        // $stub->doSomething() throws Exception
        $stub->doSomething();
    }
}
?>

Alternatively, you can write the stub yourself and improve your design along the way. Widely used resources are accessed through a single façade, so you can easily replace the resource with the stub. For example, instead of having direct database calls scattered throughout the code, you have a single Database object, an implementor of the IDatabase interface. Then, you can create a stub implementation of IDatabase and use it for your tests. You can even create an option for running the tests with the stub database or the real database, so you can use your tests for both local testing during development and integration testing with the real database.

Functionality that needs to be stubbed out tends to cluster in the same object, improving cohesion. By presenting the functionality with a single, coherent interface you reduce the coupling with the rest of the system.

Mock Objects

The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is refered to as mocking.

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. Therefore, a mock object is lot more than just a test stub plus assertions; it is used a fundamentally different way".

Here is an example: suppose we want to test that the correct method, update() in our example, is called on an object that observes another object. Example 10.10 shows the code for the Subject and Observer classes that are part of the System under Test (SUT).

Example 10.10: The Subject and Observer classes that are part of the System under Test (SUT)

<?php
class Subject
{
    protected $observers = array();
 
    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.
}
?>

Example 10.11 shows how to use a mock object to test the interaction between Subject and Observer objects.

We first use the getMock() method that is provided by the PHPUnit_Framework_TestCase class to set up a mock object for the Observer. Since we give an array as the second (optional) parameter for the getMock() method, only the update() method of the Observer class is replaced by a mock implementation.

Example 10.11: Testing that a method gets called once and with a specified argument

<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
    public function testObserversAreUpdated()
    {
        // Create a mock for the Observer class,
        // only mock the update() method.
        $observer = $this->getMock('Observer', array('update'));
 
        // 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;
        $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();
    }
}
?>

The with() method can take any number of arguments, corresponding to the number of parameters to the method being mocked. You can specify more advanced constraints on the method argument than a simple match.

Example 10.12: Testing that a method gets called with a number of arguments constrained in different ways

<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
    public function testErrorReported()
    {
        // Create a mock for the Observer class, mocking the
        // reportError() method
        $observer = $this->getMock('Observer', array('reportError'));
 
        $observer->expects($this->once())
                 ->method('reportError')
                 ->with($this->greaterThan(0),
                        $this->stringContains('Something'),
                        $this->anything());
 
        $subject = new Subject;
        $subject->attach($observer);
 
        // The doSomethingBad() method should report an error to the observer
        // via the reportError() method
        $subject->doSomethingBad();
    }
}
?>

Table 4.3 shows the constraints that can be applied to method arguments and Table 10.1 shows the matchers that are available to specify the number of invocations.

Table 10.1. Matchers

MatcherMeaning
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any()Returns a matcher that matches when the method it is evaluated for is executed zero or more times.
PHPUnit_Framework_MockObject_Matcher_InvokedCount never()Returns a matcher that matches when the method it is evaluated for is never executed.
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce()Returns a matcher that matches when the method it is evaluated for is executed at least once.
PHPUnit_Framework_MockObject_Matcher_InvokedCount once()Returns a matcher that matches when the method it is evaluated for is executed exactly once.
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count)Returns a matcher that matches when the method it is evaluated for is executed exactly $count times.
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index)Returns a matcher that matches when the method it is evaluated for is invoked at the given $index.

The getMockForAbstractClass() method returns a mock object for an abstract class. All abstract methods of the given abstract class are mocked. This allows for testing the concrete methods of an abstract class.

Example 10.13: Testing the concrete methods of an abstract class

<?php
abstract class AbstractClass
{
    public function concreteMethod()
    {
        return $this->abstractMethod();
    }
 
    public abstract function abstractMethod();
}
 
class AbstractClassTest extends PHPUnit_Framework_TestCase
{
    public function testConcreteMethod()
    {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
             ->method('abstractMethod')
             ->will($this->returnValue(TRUE));
 
        $this->assertTrue($stub->concreteMethod());
    }
}
?>

Stubbing and Mocking Web Services

When your application interacts with a web service you want to test it without actually interacting with the web service. To make the stubbing and mocking of web services easy, the getMockFromWsdl() can be used just like getMock() (see above). The only difference is that getMockFromWsdl() returns a stub or mock based on a web service description in WSDL and getMock() returns a stub or mock based on a PHP class or interface.

Example 10.14 shows how getMockFromWsdl() can be used to stub, for example, the web service described in GoogleSearch.wsdl.

Example 10.14: Stubbing a web service

<?php
class GoogleTest extends PHPUnit_Framework_TestCase
{
    public function testSearch()
    {
        $googleSearch = $this->getMockFromWsdl(
          'GoogleSearch.wsdl', 'GoogleSearch'
        );
 
        $directoryCategory = new StdClass;
        $directoryCategory->fullViewableName = '';
        $directoryCategory->specialEncoding = '';
 
        $element = new StdClass;
        $element->summary = '';