2014-10-17 06:53:52 +02:00
title: Fixtures
summary: Populate test databases with fake seed data.
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
# Fixtures
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
To test functionality correctly, we must use consistent data. If we are testing our code with the same data each
time, we can trust our tests to yield reliable results and to identify when the logic changes. Each test run in
2014-10-17 06:53:52 +02:00
SilverStripe starts with a fresh database containing no records. `Fixtures` provide a way to describe the initial data
2017-10-27 04:38:27 +02:00
to load into the database. The [SapphireTest ](api:SilverStripe\Dev\SapphireTest ) class takes care of populating a test database with data from
2014-10-17 06:53:52 +02:00
fixtures - all we have to do is define them.
2013-09-21 21:24:32 +02:00
2017-06-28 18:24:47 +02:00
To include your fixture file in your tests, you should define it as your `$fixture_file` :
**mysite/tests/MyNewTest.php**
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
use SilverStripe\Dev\SapphireTest;
2017-10-26 02:22:02 +02:00
2017-10-27 04:38:27 +02:00
class MyNewTest extends SapphireTest
{
protected static $fixture_file = 'fixtures.yml';
}
2017-08-03 02:51:32 +02:00
```
2017-06-28 18:24:47 +02:00
2017-10-27 04:38:27 +02:00
You can also use an array of fixture files, if you want to use parts of multiple other tests.
2017-06-28 18:24:47 +02:00
2017-10-27 04:38:27 +02:00
If you are using [api:SilverStripe\Dev\TestOnly] dataobjects in your fixtures, you must
declare these classes within the $extra_dataobjects variable.
2017-06-28 18:24:47 +02:00
**mysite/tests/MyNewTest.php**
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
use SilverStripe\Dev\SapphireTest;
class MyNewTest extends SapphireTest
{
protected static $fixture_file = [
'fixtures.yml',
'otherfixtures.yml'
];
protected static $extra_dataobjects = [
Player::class,
Team::class,
];
}
2017-08-03 02:51:32 +02:00
```
2017-06-28 18:24:47 +02:00
Typically, you'd have a separate fixture file for each class you are testing - although overlap between tests is common.
2017-10-27 04:38:27 +02:00
Fixtures are defined in `YAML` . `YAML` is a markup language which is deliberately simple and easy to read, so it is
2014-10-17 06:53:52 +02:00
ideal for fixture generation. Say we have the following two DataObjects:
2013-09-21 21:24:32 +02:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
use SilverStripe\ORM\DataObject;
use SilverStripe\Dev\TestOnly;
class Player extends DataObject implements TestOnly
{
private static $db = [
'Name' => 'Varchar(255)'
];
private static $has_one = [
'Team' => 'Team'
];
}
class Team extends DataObject implements TestOnly
{
private static $db = [
'Name' => 'Varchar(255)',
'Origin' => 'Varchar(255)'
];
private static $has_many = [
'Players' => 'Player'
];
}
2017-08-03 02:51:32 +02:00
```
2013-09-21 21:24:32 +02:00
We can represent multiple instances of them in `YAML` as follows:
2014-10-17 06:53:52 +02:00
**mysite/tests/fixtures.yml**
2017-08-03 02:51:32 +02:00
```yml
2017-10-27 04:38:27 +02:00
Team:
hurricanes:
Name: The Hurricanes
Origin: Wellington
crusaders:
Name: The Crusaders
Origin: Canterbury
Player:
john:
Name: John
Team: =>Team.hurricanes
joe:
Name: Joe
Team: =>Team.crusaders
jack:
Name: Jack
Team: =>Team.crusaders
2017-08-03 02:51:32 +02:00
```
2013-09-21 21:24:32 +02:00
2017-10-27 04:38:27 +02:00
This `YAML` is broken up into three levels, signified by the indentation of each line. In the first level of
2014-10-17 06:53:52 +02:00
indentation, `Player` and `Team` , represent the class names of the objects we want to be created.
2013-09-21 21:24:32 +02:00
2017-10-27 04:38:27 +02:00
The second level, `john` /`joe`/`jack` & `hurricanes` /`crusaders`, are **identifiers** . Each identifier you specify
2014-10-17 06:53:52 +02:00
represents a new object and can be referenced in the PHP using `objFromFixture`
2013-09-21 21:24:32 +02:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$player = $this->objFromFixture('Player', 'jack');
2017-08-03 02:51:32 +02:00
```
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
The third and final level represents each individual object's fields.
2014-02-24 09:41:48 +01:00
2017-10-27 04:38:27 +02:00
A field can either be provided with raw data (such as the names for our Players), or we can define a relationship, as
2014-10-17 06:53:52 +02:00
seen by the fields prefixed with `=>` .
2014-02-24 09:41:48 +01:00
2017-10-27 04:38:27 +02:00
Each one of our Players has a relationship to a Team, this is shown with the `Team` field for each `Player` being set
2014-10-17 06:53:52 +02:00
to `=>Team.` followed by a team name.
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
< div class = "info" markdown = "1" >
Take the player John in our example YAML, his team is the Hurricanes which is represented by `=>Team.hurricanes` . This
sets the `has_one` relationship for John with with the `Team` object `hurricanes` .
< / div >
2013-09-21 21:24:32 +02:00
< div class = "hint" markdown = '1' >
2017-10-27 04:38:27 +02:00
Note that we use the name of the relationship (Team), and not the name of the
2014-02-24 09:41:48 +01:00
database field (TeamID).
2013-09-21 21:24:32 +02:00
< / div >
2016-08-23 00:42:14 +02:00
< div class = "hint" markdown = '1' >
Also be aware the target of a relationship must be defined before it is referenced, for example the `hurricanes` team must appear in the fixture file before the line `Team: =>Team.hurricanes` .
< / div >
2014-10-17 06:53:52 +02:00
This style of relationship declaration can be used for any type of relationship (i.e `has_one` , `has_many` , `many_many` ).
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
We can also declare the relationships conversely. Another way we could write the previous example is:
2013-09-21 21:24:32 +02:00
2017-08-03 02:51:32 +02:00
```yml
2017-10-27 04:38:27 +02:00
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: Hurricanes
Origin: Wellington
Players: =>Player.john
crusaders:
Name: Crusaders
Origin: Canterbury
Players: =>Player.joe,=>Player.jack
2017-08-03 02:51:32 +02:00
```
2013-09-21 21:24:32 +02:00
2017-10-27 04:38:27 +02:00
The database is populated by instantiating `DataObject` objects and setting the fields declared in the `YAML` , then
calling `write()` on those objects. Take for instance the `hurricances` record in the `YAML` . It is equivalent to
2014-10-17 06:53:52 +02:00
writing:
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$team = new Team([
'Name' => 'Hurricanes',
'Origin' => 'Wellington'
]);
2014-10-17 06:53:52 +02:00
2017-10-27 04:38:27 +02:00
$team->write();
2017-08-03 05:35:09 +02:00
2017-10-27 04:38:27 +02:00
$team->Players()->add($john);
2017-08-03 02:51:32 +02:00
```
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
< div class = "notice" markdown = "1" >
2017-10-27 04:38:27 +02:00
As the YAML fixtures will call `write` , any `onBeforeWrite()` or default value logic will be executed as part of the
2014-10-17 06:53:52 +02:00
test.
< / div >
2014-02-24 09:41:48 +01:00
2016-12-15 04:11:42 +01:00
### Fixtures for namespaced classes
As of SilverStripe 4 you will need to use fully qualfied class names in your YAML fixture files. In the above examples, they belong to the global namespace so there is nothing requires, but if you have a deeper DataObject, or it has a relationship to models that are part of the framework for example, you will need to include their namespaces:
2017-08-03 02:51:32 +02:00
```yml
2017-10-27 04:38:27 +02:00
MyProject\Model\Player:
john:
Name: join
MyProject\Model\Team:
crusaders:
Name: Crusaders
Origin: Canterbury
Players: =>MyProject\Model\Player.john
2017-08-03 02:51:32 +02:00
```
2016-12-15 04:11:42 +01:00
< div class = "notice" markdown = "1" >
2017-07-03 03:22:12 +02:00
If your tests are failing and your database has table names that follow the fully qualified class names, you've probably forgotten to implement `private static $table_name = 'Player';` on your namespaced class. This property was introduced in SilverStripe 4 to reduce data migration work. See [DataObject ](api:SilverStripe\ORM\DataObject ) for an example.
2016-12-15 04:11:42 +01:00
< / div >
2014-02-24 09:41:48 +01:00
### Defining many_many_extraFields
2017-10-27 04:38:27 +02:00
`many_many` relations can have additional database fields attached to the relationship. For example we may want to
2014-10-17 06:53:52 +02:00
declare the role each player has in the team.
2014-02-24 09:41:48 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
use SilverStripe\ORM\DataObject;
class Player extends DataObject
{
private static $db = [
'Name' => 'Varchar(255)'
];
private static $belongs_many_many = [
'Teams' => 'Team'
];
}
class Team extends DataObject
{
private static $db = [
'Name' => 'Varchar(255)'
];
private static $many_many = [
'Players' => 'Player'
];
private static $many_many_extraFields = [
'Players' => [
'Role' => "Varchar"
]
];
}
2017-08-03 02:51:32 +02:00
```
2014-02-24 09:41:48 +01:00
2014-10-17 06:53:52 +02:00
To provide the value for the `many_many_extraField` use the YAML list syntax.
2014-02-24 09:41:48 +01:00
2017-08-03 02:51:32 +02:00
```yml
2017-10-27 04:38:27 +02:00
Player:
john:
Name: John
joe:
Name: Joe
jack:
Name: Jack
Team:
hurricanes:
Name: The Hurricanes
Players:
- =>Player.john:
Role: Captain
crusaders:
Name: The Crusaders
Players:
- =>Player.joe:
Role: Captain
- =>Player.jack:
Role: Winger
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
## Fixture Factories
2017-10-27 04:38:27 +02:00
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
Alternatively, you can use the [FixtureFactory ](api:SilverStripe\Dev\FixtureFactory ) class, which allows you to set default values, callbacks on object
2014-10-17 06:53:52 +02:00
creation, and dynamic/lazy value setting.
2012-12-07 18:44:00 +01:00
2013-09-21 21:24:32 +02:00
< div class = "hint" markdown = '1' >
2016-12-15 04:11:42 +01:00
`SapphireTest` uses `FixtureFactory` under the hood when it is provided with YAML based fixtures.
2013-09-21 21:24:32 +02:00
< / div >
2017-10-27 04:38:27 +02:00
The idea is that rather than instantiating objects directly, we'll have a factory class for them. This factory can have
*blueprints* defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a
2014-10-17 06:53:52 +02:00
name, which is usually set to the class it creates such as `Member` or `Page` .
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
Blueprints are auto-created for all available DataObject subclasses, you only need to instantiate a factory to start
2014-10-17 06:53:52 +02:00
using them.
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
use SilverStripe\Core\Injector\Injector;
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
$factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('Team', 'hurricanes');
2017-08-03 02:51:32 +02:00
```
2014-10-17 06:53:52 +02:00
In order to create an object with certain properties, just add a third argument:
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$obj = $factory->createObject('Team', 'hurricanes', [
'Name' => 'My Value'
]);
2017-08-03 02:51:32 +02:00
```
2014-10-17 06:53:52 +02:00
< div class = "warning" markdown = "1" >
2017-10-27 04:38:27 +02:00
It is important to remember that fixtures are referenced by arbitrary identifiers ('hurricanes'). These are internally
2014-10-17 06:53:52 +02:00
mapped to their database identifiers.
< / div >
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
After we've created this object in the factory, `getId` is used to retrieve it by the identifier.
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$databaseId = $factory->getId('Team', 'hurricanes');
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
### Default Properties
2012-12-07 18:44:00 +01:00
2016-03-30 02:17:28 +02:00
Blueprints can be overwritten in order to customise their behavior. For example, if a Fixture does not provide a Team
2014-10-17 06:53:52 +02:00
name, we can set the default to be `Unknown Team` .
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$factory->define('Team', [
'Name' => 'Unknown Team'
]);
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
### Dependent Properties
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
Values can be set on demand through anonymous functions, which can either generate random defaults, or create composite
2014-10-17 06:53:52 +02:00
values based on other fixture data.
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$factory->define('Member', [
'Email' => function($obj, $data, $fixtures) {
if(isset($data['FirstName']) {
$obj->Email = strtolower($data['FirstName']) . '@example.org';
2017-08-07 05:11:17 +02:00
}
2017-10-27 04:38:27 +02:00
},
'Score' => function($obj, $data, $fixtures) {
$obj->Score = rand(0,10);
}
)];
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
### Relations
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
Model relations can be expressed through the same notation as in the YAML fixture format described earlier, through the
2014-10-17 06:53:52 +02:00
`=>` prefix on data values.
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$obj = $factory->createObject('Team', 'hurricanes', [
'MyHasManyRelation' => '=>Player.john,=>Player.joe'
]);
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
2013-09-21 21:24:32 +02:00
#### Callbacks
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
Sometimes new model instances need to be modified in ways which can't be expressed in their properties, for example to
2014-10-17 06:53:52 +02:00
publish a page, which requires a method call.
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
2014-10-17 06:53:52 +02:00
2017-10-27 04:38:27 +02:00
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
$obj->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
});
2014-10-17 06:53:52 +02:00
2017-10-27 04:38:27 +02:00
$page = $factory->define('Page', $blueprint);
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
Available callbacks:
* `beforeCreate($identifier, $data, $fixtures)`
* `afterCreate($obj, $identifier, $data, $fixtures)`
### Multiple Blueprints
2017-10-27 04:38:27 +02:00
Data of the same type can have variations, for example forum members vs. CMS admins could both inherit from the `Member`
class, but have completely different properties. This is where named blueprints come in. By default, blueprint names
2014-10-17 06:53:52 +02:00
equal the class names they manage.
2012-12-07 18:44:00 +01:00
2017-08-03 02:51:32 +02:00
```php
2017-10-27 04:38:27 +02:00
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
2014-10-17 06:53:52 +02:00
2017-10-27 04:38:27 +02:00
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
2014-10-17 06:53:52 +02:00
2017-10-27 04:38:27 +02:00
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
if(isset($fixtures['Group']['admin'])) {
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
$obj->Groups()->add($adminGroup);
}
});
2013-09-21 21:24:32 +02:00
2017-10-27 04:38:27 +02:00
$member = $factory->createObject('Member'); // not in admin group
2012-12-07 18:44:00 +01:00
2017-10-27 04:38:27 +02:00
$admin = $factory->createObject('AdminMember'); // in admin group
2017-08-03 02:51:32 +02:00
```
2012-12-07 18:44:00 +01:00
2014-10-17 06:53:52 +02:00
## Related Documentation
2013-09-24 22:09:30 +02:00
2015-02-28 01:09:15 +01:00
* [How to use a FixtureFactory ](how_tos/fixturefactories/ )
2013-09-24 22:09:30 +02:00
2014-10-17 06:53:52 +02:00
## API Documentation
2013-09-24 22:09:30 +02:00
2017-07-03 03:22:12 +02:00
* [FixtureFactory ](api:SilverStripe\Dev\FixtureFactory )
* [FixtureBlueprint ](api:SilverStripe\Dev\FixtureBlueprint )