Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 221 lines (156 sloc) 9.186 kb

Working with Data

Tests should not affect each other. That's a rule. When tests interact with databases they may change data inside them. This leads to data inconsistency. A test may try to insert a record that is already inserted, or retrieve a deleted record. To avoid test failures, the database should be brought to it's initial state. Codeception has different methods and approaches to get your data cleaned.

This chapter summarizes all of the notices on clean ups from previous chapters and suggests the best strategies to choose data storage backends.

When we choose to clean up a database, we should make this cleaning as fast as possible. Tests should always run fast. Rebuilding the database from scratch is not the best way, but might be the only one. In any case, you should use a special test database for testing. Do not ever test on a development or production database!

Manual Cleanup

You could possibly create records at the begining of test and delete them afterwards. This is cool option if you don't have shared data between tests. But you shouldn't put any code into your test file. Because test files are parsed two times: for analisys and execution, this may lead to unpredictable results. So, you should specify when do you want your code to be executed. It's a good idea to create data before the analisys and remove data after the test is finished. Use the special methods running() and preload() of $scenario object to determine the current object state.

<?php
if ($scenario->preload()) {
    $user = new User();
    $user->name('davert');
    $user->save();
}

$I = new WebGuy($scenario);
$I->amOnPage('/admin/users');
$I->see('User: '. $user->name);

if ($scenario->running()) {
    $user->delete();
}
?>

Similarly you can insert any code before into your test. But please explicitly set the stage when you need it to be included, or the code will be executed twice!

Automatical Cleanup

Codeception has a Db module, which takes on most of the tasks of database interaction. By default it will try to repopulate the database from a dump and clean it up after each test. This module expects a database dump in SQL format. It's already prepared for configuration in codeception.yml

modules:
    config:
        Db:
            dsn: 'PDO DSN HERE'
            user: 'root'
            password:
            dump: tests/_data/your-dump-name.sql

After you enable this module in your test suite, it will automatically populate the database from a dump and repopulate it on each test run. These settings can be changed through the populate and cleanup options, which may be set to disabled.

The Db module is a rough tool. It works for any type of database supported by PDO. It could be used for all of the tests if it wasn't so slow. Loading a dump can take a lot of time that can be saved by using other techniques. When your test and application share the same connection, as may be the case in functional and unit tests, the best way to speed up everything is either to put all of the code in transactions or use SQLite Memory. When database interactions are performed through different connections, as we do in acceptance tests, the best solution we can propose is to use an SQLite file database, replacing it instead of rebuilding it after each test.

Separate connections

In acceptance tests, your test is interacting with the application through a web server. There is no way to receive a database connection from the web server. This means that the test and the application will work with the same database but on different connections. Provide in the Db module the same credentials that your application uses, and then you can access the database for assertions (seeInDabatase actions) and perform automatic cleanups.

Speedup with SQLite

To speed up testing, we recommend that you try using SQLite in your application, converting your database dump to SQLite format. Use one of the provided tools.

Keep in mind that columns in SQLite are case-sensitive, so it's important to set COLLATE NOCASE for each text and varchar column.

Store the SQLite test database in the tests/_data directory, and point the Db module to it too:

modules:
    config:
        Db:
            dsn: 'sqlite:tests/_data/application.db'
            user: 'root'
            password:
            dump: tests/_data/sqlite_dump.sql

Before the test suite is started, the SQLite database is created and copied. After each test, the copy replaces the database and a reconnection is done.

Shared connections

When an application or it's parts are run within the Codeception process, you can use your application connection in your tests. If you can access the connection, all database operations can be put into one global transaction and rolled back at the end. That will dramatically improve performance. Nothing will be written tothe database at the end, thus no database repopulation is actually needed.

ORM modules

If your application is using an ORM like Doctrine or Doctrine2, connect the respective modules to the suite. By default they will cover everything in a transaction. If you use several database connections, or there are transactions not tracked by the ORM, that module will be useless for you.

An ORM module can be connected with a Db module, but by default both will perform cleanup. Thus you should explicitly set which module is used:

In tests/functional.suite.yml:

modules:
    enabled: [Db, Doctrine1, TestHelper]
    config:
        Db:
            cleanup: false

Still, the Db module will perform database population from a dump before each test. Use populate: false to disable it.

Dbh module

If you use PostgreSQL, or any other database which supports nested transactions, you can use the Dbh module. It takes a PDO instance from your application, starts a transaction at the beginning of the tests, and rolls it back at the end. A PDO connection can be set in the bootstrap file. This module also overrides the seeInDatabase and dontSeeInDatabase actions of the Db module.

To use the Db module for population and Dbh for cleanups, use this config:

modules:
    enabled: [Db, Dbh, TestHelper]
    config:
        Db:
            cleanup: false

Please, note that Dbh module should go after the Db. That allows the Dbh module to override actions.

Fixtures

Fixtures are sample data that we can use in tests. This data can be either generated, or taken from a sample database. Fixtures should be set in the bootstrap file. Depending on the suite, fixture definitions may change.

Fixtures for Acceptance and Functional Tests

Fixtures for acceptance and functional tests fixtures can be simply defined and used.

Fixture definition in __bootstrap.php_

<?php
// let's take user from sample database. 
// we can populate it with Db module
$davert = Doctrine::getTable('User')->findOneBy('name', 'davert');

?>

Fixture usage in a sample accetpance or functional test.

<?php
$I = new TestGuy($scenario);
$I->amLoggedAs($davert);
$I->see('Welcome, Davert');
?>

All variables from the bootstrap file are passed to the Cept files of the testing suite.

You can use the Faker library to create tested data within a bootstrap file.

Fixtures for Unit Tests.

Passing fixtures to Cest files is a bit harder, since you can't pass a variable into the class. So a special registry class Fixtures is used.

Fixture definition in __bootstrap.php_ for a unit test suite.

<?php
use \Codeception\Util\Fixtures as Fixtures;
Fixtures::add('davert', Doctrine::getTable('User')->findOneBy('name', 'davert'));
?>

In a sample unit test:

<?php
$I->execute(function () {
    $user = Fixtures::get('davert');
    return $user->getName();        
});
$I->seeResultEquals('davert');
?>

The Fixtures class stores and retrieves any variable by key. You can extend it by adding namespaces.

In __bootstrap.yml_

<?php
use \Codeception\Util\Fixtures as Fixtures;

class MyFixtures extends Fixtures {
    public static function add($namespace, $key, $value) {
        parent::add("$namespace.$key", $value);
    }    
    public static function get($namespace, $key) {
        return parent::get("$namespace.$key");
    }

    public static function getUser($key)
    {
        return parent::get("user.$key");
    }
};

MyFixtures::add('user', 'davert', Doctrine::getTable('User')->findOneBy('name', 'davert'));

?>

In a test:

<?php
$I->execute(function () {
    $user = Fixtures::get('user','davert');
    // or
    $user = Fixtures::getUser('davert');
    return $user->getName();        
});
$I->seeResultEquals('davert');
?>

Using fixtures simplifies your testing code. Name your fixtures wisely and your tests will gain in readability.

Conclusion

Codeception is not abandoning the developer when dealing with data. Tools for database population and cleanups are bundled within the Db module. To manipulate sample data in a test, use fixtures that can be defined within the bootstrap file.

Something went wrong with that request. Please try again.