The DatabaseFixtures class is an AperiTestCase fixture-decorator used to test data access classes with a real database and real data set up and torn down for each test.
Note that DataAccessFixtureTools will only work with an Aperiplus DatabaseConnection object (see below). That probably limits the potential user-base to just one—me—but the underlying ideas may be of interest to other test-infected programmers and so here it is. Feel free to pinch anything for your own test framework.
<?php
interface iDatabaseConnection {
function ping();
function close();
function query(); // anything else not listed below
function getRow(); // select (single row) returns array
function getRows(); // select (indeterminate number of rows) returns iterator
function editTable(); // delete, insert, update returns num affected
function lastInsertId();
function getError();
}
Although I have been using various versions of this for some time without any problems, it's up to you to check the code as thoroughly as you need for your own purposes. Data is precious.
Extend AperiUnitTest with your own test case class and add a beforeInvoking() method:
<?php
class YourTests extends AperiUnitTest {
function beforeInvoking() {
$this->mixin('DatabaseFixtures');
}
You've got a choice of "DatabaseFixtures" or "TableFixtures". Normally you'd use the former. DatabaseFixtures assumes you have create database privileges and hence can set up and tear down whole databases as test fixtures. TableFixtures is used when you don't have the create database privilege (eg a budget hosting account). Test fixtures are comprised of tables set up and torn down within the sole available database.
As with all test case decorators, the methods of the chosen class are magically added to the test case.
<?php
class YourTests extends AperiUnitTest {
function beforeInvoking() {
$this->mixin('DatabaseFixtures');
$this->addHost(
'MysqliConnection',
'host',
'user',
'pass');
}
The first parameter is the name of a class which implements the Aperiplus iDatabaseConnection interface (only mysql is supported at the moment). Currently you can only add a single database server but in future versions I'll look at support for multiple hosts.
If you've chosen TableFixtures, also pass the database name in addHost:
<?php
class YourTests extends AperiUnitTest {
function beforeInvoking() {
$this->mixin('TableFixtures');
$this->addHost(
'MysqliConnection',
'host',
'user',
'pass',
'database-name');
}
To do: charset issues?
Special "SandboxedConnection" objects are provided for testing. These add a query authorisation wrapper to the standard connection.
The idea is that data access tests will be isolated in a sandbox thus protecting any non-test data on the server. On a local development box, malfunctioning code can't interfere with other projects or, on a production server, you can run a few tests without risking live data.
The fixture tool, $this->fxtool(), is used solely for fixture creation:
<?php
class YourTests extends AperiUnitTest {
...
function setUp() {
$this->fxtool()->query("create database foo");
$this->fxtool()->query("create table foo.bar (a text)");
...
...
}
With DatabaseFixtures, All sandboxed connections can be told to use a default database with a single command. Don't forget to create it first. (With TableFixtures, all connections automatically use the named db).
<?php
class YourTests extends AperiUnitTest {
...
function setUp() {
$this->fxtool()->query("create database foo");
$this->useDb('foo');
...
...
}
If the fixture is complicated you might want to write (and test) an installer:
<?php
class YourTests extends AperiUnitTest {
...
function setUp() {
$installer = new MyDatabaseSchema(
$this->fxtool(),
'database_name');
$installer->install();
}
Or you could register a test case fixture. Fixture objects can easily be re-used in other tests (as can an installer).
<?php
class FooFixture {
function __construct($conn) {
$this->_conn = $conn;
}
function setUp() {
$this->_conn->query->(...);
$this->_conn->query->(...);
}
}
class YourTests extends AperiUnitTest {
function beforeInvoking() {
$this->mixin(...);
$this->addHost(...);
$this->addFixture(new FooFixture($this->fxtool()));
}
The fixture object will be plugged in to the setUp/tearDown cycle. See docs: Decorating Test Cases.
Note that a tearDown method isn't required when you use the fixture tool to create databases or tables. Everything created with the fixture tool (or any other sandboxed connection) during a test is automatically dropped after the test is run.
Use getConnection() / getConn() to obtain a fresh (sandboxed) connection object for each instance of the tested class. Each call to getConnection() returns a new object encapsulating a unique database connection.
<?php
function testSomething() {
$testable = new YourClass($this->getConn());
...
...
}
By default, the connection object will connect using the account you gave in the addHost call, above. You can choose another:
<?php
function testSomething() {
$testable = new YourClass($this->getConn('user', 'pass'));
...
...
}
At last, we can do some testing. You've got all the usual AperiUnitTest assertions plus:
assertUsedDb( $conn, $db_name, $message = '%s')
assertDbNotUsed( $conn, $db_name, $message = '%s')
assertDatabase( $db_name, $message = '%s')
assertNoDatabase( $db_name, $message = '%s')
assertTables( - asserts the exact list, $db_name, no more and no less $table_list, - array $message = '%s')
assertTable( $table_address, - dot syntax: "database.table" $message = '%s')
assertNoTable( $table_address, - dot syntax: "database.table" $message = '%s')
assertRows( - found rows must be identical $sql, to expected rows and in the $row_1, same order $row_2, ..etc... (variable num args) $message = '%s')
assertRow( $sql, $expected_row, $message = '%s')
assertNoRows( $sql, $message = '%s')
assertNumRows( $i, $table_address, - dot syntax: "database.table" $message = '%s')
assertFieldValue( $sql, $field, $expected, $message = '%s')
assertServerVar( - eg character_set_connection & etc $name, $value, $conn, $message = '%s')
assertDatabaseCharset( $database_name, $expected_charset, $message = '%s')
assertTableCharset( $table_name, $expected_charset, $message = '%s')
dumpQuery( $sql, - display a "select.." query result (debugging) $message = '')
If you need to assert data stored in a table you can simplify the code a little with a custom assertion rather than using assertRow/assertRows. Tuck the select query away in the assert method:
<?php
function assertFooRecords() {
$args = func_get_args();
array_unshift($args, "select * from foo");
return call_user_func_array(
array($this, 'assertRows'),
$args);
}
In use:
<?php
function test() {
...
$this->assertFooRecords(
array('id'=>1, 'name'=>'foo'),
array('id'=>2, 'name'=>'bar'),
// ...
// (variable num args, 1 per expected row)
);
}
If you use the sandboxed connections, the test case will automatically tidy up after itself. All fixture databases (or tables) are dropped after each test and all connections are closed (except for the fixture tool which is closed after all tests in the current test case have run). You won't need a tearDown method unless you have other, non-database items to clean up.
A fatal error in the tested class means that the test case will die before it gets a chance to tidy up. To avoid having to delete these vestigial fixtures manually (new tests must always start from a clean slate) they will be detected and dropped automatically the next time the test script is run (in fact the next time any DatabaseFixtures-decorated test is run: the same persistence mechanism is used for all and hence any one will remove fixtures left behind by any other).
Built-in skip conditions: