test-fixtures.md 15.5 KB
Newer Older
Qiang Xue committed
1 2 3
Fixtures
========

4
Fixtures are an important part of testing. Their main purpose is to set up the environment in a fixed/known state
Qiang Xue committed
5 6 7
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.

8
A key concept in the Yii fixture framework is the so-called *fixture object*. A fixture object represents
Qiang Xue committed
9 10
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
Qiang Xue committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24
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.


Defining a 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.

Qiang Xue committed
25
The following code defines a fixture about the `User` ActiveRecord and the corresponding user table.
Qiang Xue committed
26 27

```php
Qiang Xue committed
28
<?php
Qiang Xue committed
29 30 31 32 33 34
namespace app\tests\fixtures;

use yii\test\ActiveFixture;

class UserFixture extends ActiveFixture
{
35
    public $modelClass = 'app\models\User';
Qiang Xue committed
36 37 38
}
```

Qiang Xue committed
39 40 41
> 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 the `ActiveRecord` class specified by `modelClass`.
Qiang Xue committed
42

43 44 45 46 47 48
> Note: [[yii\test\ActiveFixture]] is only suited for SQL databases. For NoSQL databases, Yii provides the following
> `ActiveFixture` classes:
>
> - Mongo DB: [[yii\mongodb\ActiveFixture]]
> - Elasticsearch: [[yii\elasticsearch\ActiveFixture]] (since version 2.0.2)

Qiang Xue committed
49

Qiang Xue committed
50
The fixture data for an `ActiveFixture` fixture is usually provided in a file located at `FixturePath/data/TableName.php`,
Qiang Xue committed
51 52
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
53
`@app/tests/fixtures/data/user.php`. The data file should return an array of data rows
Qiang Xue committed
54
to be inserted into the user table. For example,
Qiang Xue committed
55 56 57 58

```php
<?php
return [
59 60 61 62 63 64 65 66 67 68 69 70
    'user1' => [
        'username' => 'lmayert',
        'email' => 'strosin.vernice@jerde.com',
        'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
        'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
    ],
    'user2' => [
        'username' => 'napoleon69',
        'email' => 'aileen.barton@heaneyschumm.com',
        'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
        'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
    ],
Qiang Xue committed
71 72 73 74 75 76 77 78 79
];
```

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.

Qiang Xue committed
80 81
> 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.
Qiang Xue committed
82

83
As we described earlier, a fixture may depend on other fixtures. For example, a `UserProfileFixture` may need to depends on `UserFixture`
Qiang Xue committed
84
because the user profile table contains a foreign key pointing to the user table.
Qiang Xue committed
85 86 87 88 89 90 91 92 93
The dependency is specified via the [[yii\test\Fixture::depends]] property, like the following,

```php
namespace app\tests\fixtures;

use yii\test\ActiveFixture;

class UserProfileFixture extends ActiveFixture
{
94 95
    public $modelClass = 'app\models\UserProfile';
    public $depends = ['app\tests\fixtures\UserFixture'];
Qiang Xue committed
96
}
Qiang Xue committed
97
```
Qiang Xue committed
98

99 100 101 102
The dependency also ensures, that the fixtures are loaded and unloaded in a well defined order. In the above example `UserFixture` will
always be loaded before `UserProfileFixture` to ensure all foreign key references exist and will be unloaded after `UserProfileFixture`
has been unloaded for the same reason.

Qiang Xue committed
103 104 105
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.
Qiang Xue committed
106 107 108 109 110


Using Fixtures
--------------

Qiang Xue committed
111
If you are using [CodeCeption](http://codeception.com/) to test your code, you should consider using
112
the `yii2-codeception` extension which has built-in support for loading and accessing fixtures.
Qiang Xue committed
113 114 115
If you are using other testing frameworks, you may use [[yii\test\FixtureTrait]] in your test cases
to achieve the same goal.

Qiang Xue committed
116
In the following we will describe how to write a `UserProfile` unit test class using `yii2-codeception`.
Qiang Xue committed
117

Qiang Xue committed
118
In your unit test class extending [[yii\codeception\DbTestCase]] or [[yii\codeception\TestCase]],
119
declare which fixtures you want to use in the [[yii\test\FixtureTrait::fixtures()|fixtures()]] method. For example,
Qiang Xue committed
120 121 122 123

```php
namespace app\tests\unit\models;

Qiang Xue committed
124
use yii\codeception\DbTestCase;
Qiang Xue committed
125 126
use app\tests\fixtures\UserProfileFixture;

Qiang Xue committed
127
class UserProfileTest extends DbTestCase
Qiang Xue committed
128
{
129 130 131 132 133 134 135 136
    public function fixtures()
    {
        return [
            'profiles' => UserProfileFixture::className(),
        ];
    }

    // ...test methods...
Qiang Xue committed
137 138 139 140 141 142
}
```

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
Qiang Xue committed
143 144
`UserProfileFixture` depends on `UserFixture`, when running any test method in the test class,
two fixtures will be loaded sequentially: `UserFixture` and `UserProfileFixture`.
Qiang Xue committed
145

Qiang Xue committed
146 147 148 149 150 151
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.
Qiang Xue committed
152 153 154 155 156 157 158 159 160 161 162 163

Because `UserProfileFixture` extends from `ActiveFixture`, you may further use the following syntax to access
the data provided by the fixture:

```php
// 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) ...
```
Qiang Xue committed
164 165 166

> Info: `$this->profiles` is still of `UserProfileFixture` type. The above access features are implemented
> through PHP magic methods.
Qiang Xue committed
167 168 169 170 171 172


Defining and Using Global Fixtures
----------------------------------

The fixtures described above are mainly used by individual test cases. In most cases, you also need some global
Qiang Xue committed
173 174 175 176
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`;
Johnny Theill committed
177
* Disable the database integrity check before loading other DB fixtures, and re-enable it after other DB fixtures are unloaded.
Qiang Xue committed
178 179

Using global fixtures is similar to using non-global ones. The only difference is that you declare these fixtures
Qiang Xue committed
180
in [[yii\codeception\TestCase::globalFixtures()]] instead of `fixtures()`. When a test case loads fixtures, it will
Qiang Xue committed
181 182 183
first load global fixtures and then non-global ones.

By default, [[yii\codeception\DbTestCase]] already declares `InitDbFixture` in its `globalFixtures()` method.
Qiang Xue committed
184 185 186 187
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.


Qiang Xue committed
188 189
Organizing Fixture Classes and Data Files
-----------------------------------------
Mark committed
190

Qiang Xue committed
191 192 193 194 195
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,
Mark committed
196

Qiang Xue committed
197 198
```
# under folder tests\unit\fixtures
Mark committed
199 200

data\
201 202 203 204 205 206 207 208 209 210
    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
Qiang Xue committed
211
# and so on
Mark committed
212 213
```

Qiang Xue committed
214 215 216 217
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
218
> from [[yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names;
219
> If you are extending from [[yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names.
Mark committed
220

Qiang Xue committed
221 222
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.
Mark committed
223 224


Qiang Xue committed
225 226 227
Summary
-------

228 229
> Note: This section is under development.

Qiang Xue committed
230 231 232 233 234 235 236 237
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:

1. Use `yii migrate` tool to upgrade your test database to the latest version;
2. Run a test case:
   - Load fixtures: clean up the relevant DB tables and populate them with fixture data;
   - Perform the actual test;
   - Unload fixtures.
Qiang Xue committed
238
3. Repeat Step 2 until all tests finish.
Qiang Xue committed
239

Qiang Xue committed
240 241 242 243 244 245

**To be cleaned up below**

Managing Fixtures
=================

246 247 248
> Note: This section is under development.
>
> todo: this tutorial may be merged with the above part of test-fixtures.md
Qiang Xue committed
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

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 format
---------------

Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md) 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' => 'strosin.vernice@jerde.com',
        'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
        'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
    ],
    [
        'name' => 'Celestine',
        'login' => 'napoleon69',
        'email' => 'aileen.barton@heaneyschumm.com',
        '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](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixture.md).
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Fixture classes name should not be plural.

Loading fixtures
----------------

Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can
294
change this behavior with config or command options. You can exclude some fixtures due load or unload by specifying `-` before its name like `-User`.
Qiang Xue committed
295 296 297 298 299 300 301 302 303 304 305

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:

```
Mark committed
306
// load `User` fixture
Qiang Xue committed
307 308 309 310 311
yii fixture/load User

// same as above, because default action of "fixture" command is "load"
yii fixture User

Mark committed
312 313 314
// load several fixtures
yii fixture User UserProfile

Qiang Xue committed
315
// load all fixtures
316
yii fixture/load "*"
Qiang Xue committed
317 318

// same as above
319
yii fixture "*"
Qiang Xue committed
320

Mark committed
321
// load all fixtures except ones
322
yii fixture "*" -DoNotLoadThisOne
Qiang Xue committed
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341

// 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'
```

Unloading fixtures
------------------

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

Mark committed
342
// Unload several fixtures
Qiang Xue committed
343 344 345
yii fixture/unload User,UserProfile

// unload all fixtures
346
yii fixture/unload "*"
Mark committed
347 348

// unload all fixtures except ones
349
yii fixture/unload "*" -DoNotUnloadThisOne
Mark committed
350

Qiang Xue committed
351 352
```

Mark committed
353
Same command options like: `namespace`, `globalFixtures` also can be applied to this command.
Qiang Xue committed
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379

Configure Command Globally
--------------------------
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'
        ],
    ],
]
```

Auto-generating fixtures
------------------------

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](https://github.com/fzaninotto/Faker) library and `yii2-faker` extension.
See extension [guide](https://github.com/yiisoft/yii2/tree/master/extensions/faker) for more docs.