Commit bbe33346 by Qiang Xue

finished fixture feature.

parent 18291c5b
...@@ -37,7 +37,7 @@ return [ ...@@ -37,7 +37,7 @@ return [
]; ];
``` ```
This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reseted. This data will be loaded to the `users`, but before it will be loaded table `users` will be cleared: all data deleted, sequence reset.
Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures).
Applying fixtures Applying fixtures
......
Fixtures
========
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 is an instance
of [[yii\test\Fixture]] or its child class. It represents a particular aspect of a test environment. For example,
you may define `UserFixture` to initialize the user database table with a set of known 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.
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.
If you extend from [[yii\test\Fixture]], make sure you override the [[yii\test\Fixture::load()]] method
with your custom code of setting up the test environment (e.g. creating specific directories or files).
In the following, we will mainly describe how to define a database fixture by extending [[yii\test\ActiveFixture]].
Each `ActiveFixture` is about setting up the test data needed by a database table. You may specify the table
by setting either the [[yii\test\ActiveFixture::tableName]] property or the [[yii\test\ActiveFixture::modelClass]]
property. The latter takes the name of an `ActiveRecord` class whose associated table will be used by the fixture.
```php
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = 'app\models\User';
}
```
Next, you should provide the data needed by the user table in a file. By default, the file should be 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/tbl_user.php`, assuming the table associated with `User` is `tbl_user`.
The data file should return an array of data rows to be inserted into the user table. For example,
```php
<?php
return [
'user1' => [
'name' => 'Chase',
'login' => 'lmayert',
'email' => 'strosin.vernice@jerde.com',
'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
],
'user2' => [
'name' => 'Celestine',
'login' => 'napoleon69',
'email' => 'aileen.barton@heaneyschumm.com',
'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.
> Info: You may customize the location of the data file by setting the [[yii\test\ActiveFixture::dataFile]] property.
> If you set this property to be false, or if you do not provide the data file, the fixture will not load any data
> into the user table.
As we described earlier, a fixture may depend on other fixtures. For example, `UserProfileFixture` depends on `UserFixture`.
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
{
public $modelClass = 'app\models\UserProfile';
public $depends = ['yii\test\DbFixture', 'app\tests\fixtures\UserFixture'];
}
```
Note that in the above, besides `app\tests\fixtures\UserFixture`, the dependency of `UserProfileFixture` also includes
`yii\test\DbFixture`. This is required by all `ActiveFixture` classes which set `yii\test\DbFixture` as the default value
of the `depends` property. The `DbFixture` class is responsible for toggling database integrity check and executing
an initialization script. Without `DbFixture`, you may not be able to freely inserting or deleting rows in a table
due to various DB constraints.
Using Fixtures
--------------
Yii provides [[yii\test\FixtureTrait]] which can be plugged into your test classes to let you easily load and access
fixtures. More often you would develop your test cases by using the `yii2-codeception` extension
which has the built-in support for the loading and accessing fixtures. In the following we will describe how to do so.
In your test case class extending [[yii\codeception\TestCase]], you declare which fixtures you want to use
in the [[yii\testFixtureTrait::fixtures()|fixtures()]] method. For example,
```php
namespace app\tests\unit\models;
use yii\codeception\TestCase;
use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends TestCase
{
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` and `DbFixture`, when running any test method in the test class,
three fixtures will be loaded sequentially: `DbFixture`, `UserFixture` and `UserProfileFixture`.
When declaring a fixture in a test class, you may assign an alias to a fixture. For example, `UserProfileFixture`
is aliased as `profiles` in the above. 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:
```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) ...
```
...@@ -5,6 +5,10 @@ namespace yii\codeception; ...@@ -5,6 +5,10 @@ namespace yii\codeception;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use Codeception\TestCase\Test; use Codeception\TestCase\Test;
use yii\base\UnknownMethodException;
use yii\base\UnknownPropertyException;
use yii\test\ActiveFixture;
use yii\test\FixtureTrait;
/** /**
* TestCase is the base class for all codeception unit tests * TestCase is the base class for all codeception unit tests
...@@ -14,6 +18,8 @@ use Codeception\TestCase\Test; ...@@ -14,6 +18,8 @@ use Codeception\TestCase\Test;
*/ */
class TestCase extends Test class TestCase extends Test
{ {
use FixtureTrait;
/** /**
* @var array|string the application configuration that will be used for creating an application instance for each test. * @var array|string the application configuration that will be used for creating an application instance for each test.
* You can use a string to represent the file path or path alias of a configuration file. * You can use a string to represent the file path or path alias of a configuration file.
...@@ -29,6 +35,7 @@ class TestCase extends Test ...@@ -29,6 +35,7 @@ class TestCase extends Test
{ {
parent::setUp(); parent::setUp();
$this->mockApplication(); $this->mockApplication();
$this->loadFixtures();
} }
/** /**
...@@ -36,11 +43,51 @@ class TestCase extends Test ...@@ -36,11 +43,51 @@ class TestCase extends Test
*/ */
protected function tearDown() protected function tearDown()
{ {
$this->unloadFixtures();
$this->destroyApplication(); $this->destroyApplication();
parent::tearDown(); parent::tearDown();
} }
/** /**
* Returns the value of an object property.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `$value = $object->property;`.
* @param string $name the property name
* @return mixed the property value
* @throws UnknownPropertyException if the property is not defined
*/
public function __get($name)
{
$fixture = $this->getFixture($name);
if ($fixture !== null) {
return $fixture;
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
/**
* Calls the named method which is not a class method.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when an unknown method is being invoked.
* @param string $name the method name
* @param array $params method parameters
* @throws UnknownMethodException when calling unknown method
* @return mixed the method return value
*/
public function __call($name, $params)
{
$fixture = $this->getFixture($name);
if ($fixture instanceof ActiveFixture) {
return $fixture->getModel(reset($params));
} else {
throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
}
}
/**
* Mocks up the application instance. * Mocks up the application instance.
* @param array $config the configuration that should be used to generate the application instance. * @param array $config the configuration that should be used to generate the application instance.
* If null, [[appConfig]] will be used. * If null, [[appConfig]] will be used.
......
...@@ -24,7 +24,7 @@ use yii\db\TableSchema; ...@@ -24,7 +24,7 @@ use yii\db\TableSchema;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class ActiveFixture extends Fixture class ActiveFixture extends Fixture implements \IteratorAggregate, \ArrayAccess, \Countable
{ {
/** /**
* @inheritdoc * @inheritdoc
...@@ -210,4 +210,64 @@ class ActiveFixture extends Fixture ...@@ -210,4 +210,64 @@ class ActiveFixture extends Fixture
$this->db->createCommand()->resetSequence($table->fullName, 1)->execute(); $this->db->createCommand()->resetSequence($table->fullName, 1)->execute();
} }
} }
/**
* Returns an iterator for traversing the cookies in the collection.
* This method is required by the SPL interface `IteratorAggregate`.
* It will be implicitly called when you use `foreach` to traverse the collection.
* @return \ArrayIterator an iterator for traversing the cookies in the collection.
*/
public function getIterator()
{
return new \ArrayIterator($this->rows);
}
/**
* Returns the number of items in the session.
* This method is required by Countable interface.
* @return integer number of items in the session.
*/
public function count()
{
return count($this->rows);
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean
*/
public function offsetExists($offset)
{
return isset($this->rows[$offset]);
}
/**
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to retrieve element.
* @return mixed the element at the offset, null if no element is found at the offset
*/
public function offsetGet($offset)
{
return isset($this->rows[$offset]) ? $this->rows[$offset] : null;
}
/**
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to set element
* @param mixed $item the element value
*/
public function offsetSet($offset, $item)
{
$this->rows[$offset] = $item;
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to unset element
*/
public function offsetUnset($offset)
{
unset($this->rows[$offset]);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment