Note: This section is under development.
Fixtures are important part of testing. Their main purpose is to set up the environment in a fixed/known state so that your tests are repeatable and run in an expected way. Yii provides a fixture framework that allows you to define your fixtures precisely and use them easily.
A key concept in the Yii fixture framework is the so-called fixture objects. A fixture object represents
a particular aspect of a test environment and is an instance of [[yii\test\Fixture]] or its child class. For example,
you may use UserFixture
to make sure the user DB table contains a fixed set of data. You load one or multiple
fixture objects before running a test and unload them when finishing.
A fixture may depend on other fixtures, specified via its [[yii\test\Fixture::depends]] property. When a fixture is being loaded, the fixtures it depends on will be automatically loaded BEFORE the fixture; and when the fixture is being unloaded, the dependent fixtures will be unloaded AFTER the fixture.
To define a fixture, create a new class by extending [[yii\test\Fixture]] or [[yii\test\ActiveFixture]]. The former is best suited for general purpose fixtures, while the latter has enhanced features specifically designed to work with database and ActiveRecord.
The following code defines a fixture about the User
ActiveRecord and the corresponding user table.
<?php
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = 'app\models\User';
}
Tip: Each
ActiveFixture
is about preparing a DB table for testing purpose. You may specify the table by setting either the [[yii\test\ActiveFixture::tableName]] property or the [[yii\test\ActiveFixture::modelClass]] property. If the latter, the table name will be taken from theActiveRecord
class specified bymodelClass
.
The fixture data for an ActiveFixture
fixture is usually provided in a file located at FixturePath/data/TableName.php
,
where FixturePath
stands for the directory containing the fixture class file, and TableName
is the name of the table associated with the fixture. In the example above, the file should be
@app/tests/fixtures/data/user.php
. The data file should return an array of data rows
to be inserted into the user table. For example,
<?php
return [
'user1' => [
'username' => 'lmayert',
'email' => '[email protected]',
'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
],
'user2' => [
'username' => 'napoleon69',
'email' => '[email protected]',
'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
],
];
You may give an alias to a row so that later in your test, you may refer to the row via the alias. In the above example,
the two rows are aliased as user1
and user2
, respectively.
Also, you do not need to specify the data for auto-incremental columns. Yii will automatically fill the actual values into the rows when the fixture is being loaded.
Tip: You may customize the location of the data file by setting the [[yii\test\ActiveFixture::dataFile]] property. You may also override [[yii\test\ActiveFixture::getData()]] to provide the data.
As we described earlier, a fixture may depend on other fixtures. For example, UserProfileFixture
depends on UserFixture
because the user profile table contains a foreign key pointing to the user table.
The dependency is specified via the [[yii\test\Fixture::depends]] property, like the following,
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserProfileFixture extends ActiveFixture
{
public $modelClass = 'app\models\UserProfile';
public $depends = ['app\tests\fixtures\UserFixture'];
}
In the above, we have shown how to define a fixture about a DB table. To define a fixture not related with DB (e.g. a fixture about certain files and directories), you may extend from the more general base class [[yii\test\Fixture]] and override the [[yii\test\Fixture::load()|load()]] and [[yii\test\Fixture::unload()|unload()]] methods.
If you are using CodeCeption to test your code, you should consider using
the yii2-codeception
extension which has the built-in support for loading and accessing fixtures.
If you are using other testing frameworks, you may use [[yii\test\FixtureTrait]] in your test cases
to achieve the same goal.
In the following we will describe how to write a UserProfile
unit test class using yii2-codeception
.
In your unit test class extending [[yii\codeception\DbTestCase]] or [[yii\codeception\TestCase]], declare which fixtures you want to use in the [[yii\test\FixtureTrait::fixtures()|fixtures()]] method. For example,
namespace app\tests\unit\models;
use yii\codeception\DbTestCase;
use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends DbTestCase
{
public function fixtures()
{
return [
'profiles' => UserProfileFixture::className(),
];
}
// ...test methods...
}
The fixtures listed in the fixtures()
method will be automatically loaded before running every test method
in the test case and unloaded after finishing every test method. And as we described before, when a fixture is
being loaded, all its dependent fixtures will be automatically loaded first. In the above example, because
UserProfileFixture
depends on UserFixture
, when running any test method in the test class,
two fixtures will be loaded sequentially: UserFixture
and UserProfileFixture
.
When specifying fixtures in fixtures()
, you may use either a class name or a configuration array to refer to
a fixture. The configuration array will let you customize the fixture properties when the fixture is loaded.
You may also assign an alias to a fixture. In the above example, the UserProfileFixture
is aliased as profiles
.
In the test methods, you may then access a fixture object using its alias. For example, $this->profiles
will
return the UserProfileFixture
object.
Because UserProfileFixture
extends from ActiveFixture
, you may further use the following syntax to access
the data provided by the fixture:
// returns the data row aliased as 'user1'
$row = $this->profiles['user1'];
// returns the UserProfile model corresponding to the data row aliased as 'user1'
$profile = $this->profiles('user1');
// traverse every data row in the fixture
foreach ($this->profiles as $row) ...
Info:
$this->profiles
is still ofUserProfileFixture
type. The above access features are implemented through PHP magic methods.
The fixtures described above are mainly used by individual test cases. In most cases, you also need some global fixtures that are applied to ALL or many test cases. An example is [[yii\test\InitDbFixture]] which does two things:
- Perform some common initialization tasks by executing a script located at
@app/tests/fixtures/initdb.php
; - Disable the database integrity check before loading other DB fixtures, and re-enable it after other DB fixtures are unloaded.
Using global fixtures is similar to using non-global ones. The only difference is that you declare these fixtures
in [[yii\codeception\TestCase::globalFixtures()]] instead of fixtures()
. When a test case loads fixtures, it will
first load global fixtures and then non-global ones.
By default, [[yii\codeception\DbTestCase]] already declares InitDbFixture
in its globalFixtures()
method.
This means you only need to work with @app/tests/fixtures/initdb.php
if you want to do some initialization work
before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures.
By default, fixture classes look for the corresponding data files under the data
folder which is a sub-folder
of the folder containing the fixture class files. You can follow this convention when working with simple projects.
For big projects, chances are that you often need to switch different data files for the same fixture class for
different tests. We thus recommend that you organize the data files in a hierarchical way that is similar to
your class namespaces. For example,
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
In this way you will avoid collision of fixture data files between tests and use them as you need.
Note: In the example above fixture files are named only for example purpose. In real life you should name them according to which fixture class your fixture classes are extending from. For example, if you are extending from [[yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names; If you are extending for [[yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
The similar hierarchy can be used to organize fixture class files. Instead of using data
as the root directory, you may
want to use fixtures
as the root directory to avoid conflict with the data files.
In the above, we have described how to define and use fixtures. Below we summarize the typical workflow of running unit tests related with DB:
- Use
yii migrate
tool to upgrade your test database to the latest version; - Run a test case:
- Load fixtures: clean up the relevant DB tables and populate them with fixture data;
- Perform the actual test;
- Unload fixtures.
- Repeat Step 2 until all tests finish.
To be cleaned up below
// todo: this tutorial may be merged into test-fixture.md
Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing different cases. With this data using your tests becoming more efficient and useful.
Yii supports fixtures via the yii fixture
command line tool. This tool supports:
- Loading fixtures to different storage such as: RDBMS, NoSQL, etc;
- Unloading fixtures in different ways (usually it is clearing storage);
- Auto-generating fixtures and populating it with random data.
Fixtures are objects with different methods and configurations, refer to official documentation on them. Lets assume we have fixtures data to load:
#users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [
[
'name' => 'Chase',
'login' => 'lmayert',
'email' => '[email protected]',
'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
],
[
'name' => 'Celestine',
'login' => 'napoleon69',
'email' => '[email protected]',
'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
],
];
If we are using fixture that loads data into database then these rows will be applied to users
table. If we are using nosql fixtures, for example mongodb
fixture, then this data will be applied to users
mongodb collection. In order to learn about implementing various loading strategies and more, refer to official documentation.
Above fixture example was auto-generated by yii2-faker
extension, read more about it in these section.
Fixture classes name should not be plural.
Fixture classes should be suffixed by Fixture
class. By default fixtures will be searched under tests\unit\fixtures
namespace, you can
change this behavior with config or command options. Note that you can also append fixtures data to already existing ones with command
option --append
. You can exclude some fixtures due load or unload by specifying -
before its name like -User
.
To load fixture, run the following command:
yii fixture/load <fixture_name>
The required fixture_name
parameter specifies a fixture name which data will be loaded. You can load several fixtures at once.
Below are correct formats of this command:
// load `User` fixture
yii fixture/load User
// same as above, because default action of "fixture" command is "load"
yii fixture User
// load several fixtures
yii fixture User UserProfile
//load fixture, but don't clean storage before load and just append to already existed data
yii fixture User --append
// load all fixtures
yii fixture/load "*"
// same as above
yii fixture "*"
// load all fixtures except ones
yii fixture "*" -DoNotLoadThisOne
// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixture User --namespace='alias\my\custom\namespace'
// load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded.
// By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several
// global fixtures separated by comma.
yii fixture User --globalFixtures='some\name\space\Custom'
To unload fixture, run the following command:
// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/unload User
// Unload several fixtures
yii fixture/unload User,UserProfile
// unload all fixtures
yii fixture/unload "*"
// unload all fixtures except ones
yii fixture/unload "*" -DoNotUnloadThisOne
Same command options like: namespace
, globalFixtures
also can be applied to this command.
While command line options allow us to configure the migration command on-the-fly, sometimes we may want to configure the command once for all. For example you can configure different migration path as follows:
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\controllers\FixtureController',
'namespace' => 'myalias\some\custom\namespace',
'globalFixtures' => [
'some\name\space\Foo',
'other\name\space\Bar'
],
],
]
Yii also can auto-generate fixtures for you based on some template. You can generate your fixtures with different data on different languages and formats.
These feature is done by Faker library and yii2-faker
extension.
See extension guide for more docs.