Zend_Test_PHPUnit_DbCoupling of data-access and the domain model often requires the use of a database for testing purposes. But the database is persistent across different tests which leads to test results that can affect each other. Furthermore setting up the database to be able to run a test is quite some work. PHPUnit's Database extension simplifies testing with a database by offering a very simple mechanism to set up and teardown the database between different tests. This component extends the PHPUnit Database extension with Zend Framework specific code, such that writing database tests against a Zend Framework application is simplified. Database Testing can be explained with two conceptual entities, DataSets and DataTables. Internally the PHPUnit Database extension can build up an object structure of a database, its tables and containing rows from configuration files or the real database content. This abstract object graph can then be compared using assertions. A common use-case in database testing is setting up some tables with seed data, then performing some operations, and finally asserting that the operated on database-state is equal to some predefined expected state. Zend_Test_PHPUnit_Db simplifies this task by allowing to generate DataSets and DataTables from existing Zend_Db_Table_Abstract or Zend_Db_Table_Rowset_Abstract instances. Furthermore this component allows to integrate any Zend_Db_Adapter_Abstract for testing whereas the original extension only works with PDO. A Test Adapter implementation for Zend_Db_Adapter_Abstract is also included in this component. It allows to instantiate a Db Adapter that requires no database at all and acts as an SQL and result stack which is used by the API methods. QuickstartSetup a Database TestCaseWe are now writting some database tests for the Bug Database example in the Zend_Db_Table documentation. First we begin to test that inserting a new bug is actually saved in the database correctly. First we have to setup a test-class that extends Zend_Test_PHPUnit_DatabaseTestCase. This class extends the PHPUnit Database Extension, which in turn extends the basic PHPUnit_Framework_TestCase. A database testcase contains two abstract methods that have to be implemented, one for the database connection and one for the initial dataset that should be used as seed or fixture.
Here we create the database connection and seed some data into the database. Some important details should be noted on this code:
Specify a seed datasetIn the previous setup for the database testcase we have specified a seed file for the database fixture. We now create this file specified in the Flat XML format:
We will work with this four entries in the database table "zfbugs" in the next examples. The required MySQL schema for this example is:
A few initial database testsNow that we have implemented the two required abstract methods of the Zend_Test_PHPUnit_DatabaseTestCase and specified the seed database content, which will be re-created for each new test, we can go about to make our first assertion. This will be a test to insert a new bug.
Now up to the $bugsTable->insert($data); everything looks familiar. The lines after that contain the assertion methodname. We want to verify that after inserting the new bug the database has been updated correctly with the given data. For this we create a Zend_Test_PHPUnit_Db_DataSet_QueryDataSet instance and give it a database connection. We will then tell this dataset that it contains a table "zfbugs" which is given by an SQL statement. This current/actual state of the database is compared to the expected database state which is contained in another XML file "bugsInsertIntoAssertions.xml". This XML file is a slight deviation from the one given above and contains another row with the expected data:
There are other ways to assert that the current database state equals an expected state. The "Bugs" table in the example already knows a lot about its inner state, so why not use this to our advantage? The next example will assert that deleting from the database is possible:
We have created a Zend_Test_PHPUnit_Db_DataSet_DbTableDataSet dataset here, which takes any Zend_Db_Table_Abstract instance and adds it to the dataset with its table name, in this example "zfbugs". You could add several tables more if you wanted using the method addTable() if you want to check for expected database state in more than one table. Here we only have one table and check against an expected database state in "bugsDeleteAssertion.xml" which is the original seed dataset without the row with id 4. Since we have only checked that two specific tables (not datasets) are equal in the previous examples we should also look at how to assert that two tables are equal. Therefore we will add another test to our TestCase which verifies updating behaviour of a dataset.
Here we create the current database state from a Zend_Db_Table_Rowset_Abstract instance in conjunction with the Zend_Test_PHPUnit_Db_DataSet_DbRowset($rowset) instance which creates an internal data-representation of the rowset. This can again be compared against another data-table by using the $this->assertTablesEqual() assertion. Usage, API and Extensions PointsThe Quickstart already gave a good introduction on how database testing can be done using PHPUnit and the Zend Framework. This section gives an overview over the API that the Zend_Test_PHPUnit_Db component comes with and how it works internally.
The Zend_Test_PHPUnit_DatabaseTestCase classThe Zend_Test_PHPUnit_DatabaseTestCase class derives from the PHPUnit_Extensions_Database_TestCase which allows to setup tests with a fresh database fixture on each run easily. The Zend implementation offers some additional convenience features over the PHPUnit Database extension when it comes to using Zend_Db resources inside your tests. The workflow of a database test-case can be described as follows.
The Zend_Test_PHPUnit_DatabaseTestCase class has some convenience functions that can help writing tests that interact with the database and the database testing extension. The next table lists only the new methods compared to the PHPUnit_Extensions_Database_TestCase, whose » API is documented in the PHPUnit Documentation.
Integrating Database Testing with the ControllerTestCaseBecause PHP does not support multiple inheritance it is not possible to use the Controller and Database testcases in conjunction. However you can use the Zend_Test_PHPUnit_Db_SimpleTester database tester in your controller test-case to setup a database enviroment fixture for each new controller test. The Database TestCase in general is only a set of convenience functions which can also be accessed and used without the test case. Example #1 Database integration example This example extends the User Controller Test from the Zend_Test_PHPUnit_ControllerTestCase documentation to include a database setup.
Now the Flat XML dataset "initialUserFixture.xml" is used to set the database into an initial state before each test, exactly as the DatabaseTestCase works internally. Using the Database Testing AdapterThere are times when you don't want to test parts of your application with a real database, but are forced to because of coupling. The Zend_Test_DbAdapter offers a convenient way to use a implementation of Zend_Db_Adapter_Abstract without having to open a database connection. Furthermore this Adapter is very easy to mock from within your PHPUnit testsuite, since it requires no constructor arguments. The Test Adapter acts as a stack for various database results. Its order of results have to be userland implemented, which might be a tedious task for tests that call many different database queries, but its just the right helper for tests where only a handful of queries are executed and you know the exact order of the results that have to be returned to your userland code.
Behaviour of any real database adapter is simulated as much as possible such that methods like fetchAll(), fetchObject(), fetchColumn and more are working for the test adapter. You can also put INSERT, UPDATE and DELETE statement onto the result stack, these however only return a statement which allows to specifiy the result of $stmt->rowCount(). By default the query profiler is enabled, so that you can retrieve the executed SQL statements and their bound parameters to check for the correctness of the execution.
The test adapter never checks if the query specified is really of the type SELECT, DELETE, INSERT or UPDATE which is returned next from the stack. The correct order of returning the data has to be implemented by the user of the test adapter. The Test adapter also specifies methods to simulate the use of the methods listTables(), describeTables() and lastInsertId(). Additionally using the setQuoteIdentifierSymbol() you can specify which symbol should be used for quoting, by default none is used.
|