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

4
> Note: This section is under development.
Qiang Xue committed
5

Qiang Xue committed
6 7 8 9
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.

Qiang Xue committed
10 11 12
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
Qiang Xue committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26
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
27
The following code defines a fixture about the `User` ActiveRecord and the corresponding user table.
Qiang Xue committed
28 29

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

use yii\test\ActiveFixture;

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

Qiang Xue committed
41 42 43
> 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
44

Qiang Xue committed
45

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

```php
<?php
return [
55 56 57 58 59 60 61 62 63 64 65 66
    '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
67 68 69 70 71 72 73 74 75
];
```

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
76 77
> 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
78

Qiang Xue committed
79 80
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.
Qiang Xue committed
81 82 83 84 85 86 87 88 89
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
{
90 91
    public $modelClass = 'app\models\UserProfile';
    public $depends = ['app\tests\fixtures\UserFixture'];
Qiang Xue committed
92
}
Qiang Xue committed
93
```
Qiang Xue committed
94 95 96 97

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
98 99 100 101 102


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

Qiang Xue committed
103 104 105 106 107
If you are using [CodeCeption](http://codeception.com/) 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.

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

Qiang Xue committed
110
In your unit test class extending [[yii\codeception\DbTestCase]] or [[yii\codeception\TestCase]],
111
declare which fixtures you want to use in the [[yii\test\FixtureTrait::fixtures()|fixtures()]] method. For example,
Qiang Xue committed
112 113 114 115

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

Qiang Xue committed
116
use yii\codeception\DbTestCase;
Qiang Xue committed
117 118
use app\tests\fixtures\UserProfileFixture;

Qiang Xue committed
119
class UserProfileTest extends DbTestCase
Qiang Xue committed
120
{
121 122 123 124 125 126 127 128
    public function fixtures()
    {
        return [
            'profiles' => UserProfileFixture::className(),
        ];
    }

    // ...test methods...
Qiang Xue committed
129 130 131 132 133 134
}
```

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
135 136
`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
137

Qiang Xue committed
138 139 140 141 142 143
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
144 145 146 147 148 149 150 151 152 153 154 155

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
156 157 158

> Info: `$this->profiles` is still of `UserProfileFixture` type. The above access features are implemented
> through PHP magic methods.
Qiang Xue committed
159 160 161 162 163 164


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
165 166 167 168
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
169
* Disable the database integrity check before loading other DB fixtures, and re-enable it after other DB fixtures are unloaded.
Qiang Xue committed
170 171

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

By default, [[yii\codeception\DbTestCase]] already declares `InitDbFixture` in its `globalFixtures()` method.
Qiang Xue committed
176 177 178 179
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
180 181
Organizing Fixture Classes and Data Files
-----------------------------------------
Mark committed
182

Qiang Xue committed
183 184 185 186 187
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
188

Qiang Xue committed
189 190
```
# under folder tests\unit\fixtures
Mark committed
191 192

data\
193 194 195 196 197 198 199 200 201 202
    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
203
# and so on
Mark committed
204 205
```

Qiang Xue committed
206 207 208 209
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
210 211
> 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.
Mark committed
212

Qiang Xue committed
213 214
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
215 216


Qiang Xue committed
217 218 219 220 221 222 223 224 225 226 227
Summary
-------

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
228
3. Repeat Step 2 until all tests finish.
Qiang Xue committed
229

Qiang Xue committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 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

**To be cleaned up below**

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

// 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 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
282
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
283 284 285 286 287 288 289 290 291 292 293

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
294
// load `User` fixture
Qiang Xue committed
295 296 297 298 299
yii fixture/load User

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

Mark committed
300 301 302
// load several fixtures
yii fixture User UserProfile

Qiang Xue committed
303
// load all fixtures
304
yii fixture/load "*"
Qiang Xue committed
305 306

// same as above
307
yii fixture "*"
Qiang Xue committed
308

Mark committed
309
// load all fixtures except ones
310
yii fixture "*" -DoNotLoadThisOne
Qiang Xue committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

// 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
330
// Unload several fixtures
Qiang Xue committed
331 332 333
yii fixture/unload User,UserProfile

// unload all fixtures
334
yii fixture/unload "*"
Mark committed
335 336

// unload all fixtures except ones
337
yii fixture/unload "*" -DoNotUnloadThisOne
Mark committed
338

Qiang Xue committed
339 340
```

Mark committed
341
Same command options like: `namespace`, `globalFixtures` also can be applied to this command.
Qiang Xue committed
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367

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.