NEW FixtureFactory separated out from YamlFixture

Enables more generic use of the fixture facilities
without dependency on the YAML format, for example
when creating fixtures from Behat step definitions.

Note: The YamlFixture class needs to be created via
Injector::inst()->create('YamlFixture') now,
direct instantiation is no longer supported.
This commit is contained in:
Ingo Schommer 2012-12-07 18:44:00 +01:00
parent 0f8b229d6b
commit e8fbfc0bd1
12 changed files with 1224 additions and 304 deletions

267
dev/FixtureBlueprint.php Normal file
View File

@ -0,0 +1,267 @@
<?php
/**
* A blueprint on how to create instances of a certain {@link DataObject} subclass.
*
* Relies on a {@link FixtureFactory} to manage database relationships between instances,
* and manage the mappings between fixture identifiers and their database IDs.
*
* @package framework
* @subpackage core
*/
class FixtureBlueprint {
/**
* @var array Map of field names to values. Supersedes {@link DataObject::$defaults}.
*/
protected $defaults = array();
/**
* @var String Arbitrary name by which this fixture type can be referenced.
*/
protected $name;
/**
* @var String Subclass of {@link DataObject}
*/
protected $class;
/**
* @var array
*/
protected $callbacks = array(
'beforeCreate' => array(),
'afterCreate' => array(),
);
static $dependencies = array(
'factory' => '%$FixtureFactory'
);
/**
* @param String $name
* @param String $class Defaults to $name
* @param array $defaults
*/
public function __construct($name, $class = null, $defaults = array()) {
if(!$class) $class = $name;
if(!is_subclass_of($class, 'DataObject')) {
throw new InvalidArgumentException(sprintf(
'Class "%s" is not a valid subclass of DataObject',
$class
));
}
$this->name = $name;
$this->class = $class;
$this->defaults = $defaults;
}
/**
* @param String $identifier Unique identifier for this fixture type
* @param Array $data Map of property names to their values.
* @param Array $fixtures Map of fixture names to an associative array of their in-memory
* identifiers mapped to their database IDs. Used to look up
* existing fixtures which might be referenced in the $data attribute
* via the => notation.
* @return DataObject
*/
public function createObject($identifier, $data = null, $fixtures = null) {
// We have to disable validation while we import the fixtures, as the order in
// which they are imported doesnt guarantee valid relations until after the import is complete.
$validationenabled = DataObject::get_validation_enabled();
DataObject::set_validation_enabled(false);
$this->invokeCallbacks('beforeCreate', array($identifier, &$data, &$fixtures));
try {
$class = $this->class;
$obj = DataModel::inst()->$class->newObject();
// If an ID is explicitly passed, then we'll sort out the initial write straight away
// This is just in case field setters triggered by the population code in the next block
// Call $this->write(). (For example, in FileTest)
if(isset($data['ID'])) {
$obj->ID = $data['ID'];
// The database needs to allow inserting values into the foreign key column (ID in our case)
$conn = DB::getConn();
if(method_exists($conn, 'allowPrimaryKeyEditing')) {
$conn->allowPrimaryKeyEditing(ClassInfo::baseDataClass($class), true);
}
$obj->write(false, true);
if(method_exists($conn, 'allowPrimaryKeyEditing')) {
$conn->allowPrimaryKeyEditing(ClassInfo::baseDataClass($class), false);
}
}
// Populate defaults
if($this->defaults) foreach($this->defaults as $fieldName => $fieldVal) {
if(isset($data[$fieldName]) && $data[$fieldName] !== false) continue;
if(is_callable($fieldVal)) {
$obj->$fieldName = $fieldVal($obj, $data, $fixtures);
} else {
$obj->$fieldName = $fieldVal;
}
}
// Populate overrides
if($data) foreach($data as $fieldName => $fieldVal) {
// Defer relationship processing
if($obj->many_many($fieldName) || $obj->has_many($fieldName) || $obj->has_one($fieldName)) {
continue;
}
$this->setValue($obj, $fieldName, $fieldVal, $fixtures);
}
$obj->write();
// Save to fixture before relationship processing in case of reflexive relationships
if(!isset($fixtures[$class])) {
$fixtures[$class] = array();
}
$fixtures[$class][$identifier] = $obj->ID;
// Populate all relations
if($data) foreach($data as $fieldName => $fieldVal) {
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
$parsedItems = array();
$items = preg_split('/ *, */',trim($fieldVal));
foreach($items as $item) {
// Check for correct format: =><relationname>.<identifier>
if(!preg_match('/^=>[^\.]+\.[^\.]+/', $item)) {
throw new InvalidArgumentException(sprintf(
'Invalid format for relation "%s" on class "%s" ("%s")',
$fieldName,
$class,
$item
));
}
$parsedItems[] = $this->parseValue($item, $fixtures);
}
$obj->write();
if($obj->has_many($fieldName)) {
$obj->getComponents($fieldName)->setByIDList($parsedItems);
} elseif($obj->many_many($fieldName)) {
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
}
} elseif($obj->has_one($fieldName)) {
// Sets has_one with relation name
$obj->{$fieldName . 'ID'} = $this->parseValue($fieldVal, $fixtures);
} elseif($obj->has_one(preg_replace('/ID$/', '', $fieldName))) {
// Sets has_one with database field
$obj->$fieldName = $this->parseValue($fieldVal, $fixtures);
}
}
$obj->write();
// If LastEdited was set in the fixture, set it here
if($data && array_key_exists('LastEdited', $data)) {
$edited = $this->parseValue($data['LastEdited'], $fixtures);
DB::manipulate(array(
$class => array(
"command" => "update", "id" => $obj->id,
"fields" => array("LastEdited" => "'".$edited."'")
)
));
}
} catch(Exception $e) {
DataObject::set_validation_enabled($validationenabled);
throw $e;
}
DataObject::set_validation_enabled($validationenabled);
$this->invokeCallbacks('afterCreate', array($obj, $identifier, &$data, &$fixtures));
return $obj;
}
/**
* @param Array $defaults
*/
public function setDefaults($defaults) {
$this->defaults = $defaults;
return $this;
}
/**
* @return Array
*/
public function getDefaults() {
return $this->defaults;
}
/**
* @return String
*/
public function getClass() {
return $this->class;
}
/**
* See class documentation.
*
* @param String $type
* @param callable $callback
*/
public function addCallback($type, $callback) {
if(!array_key_exists($type, $this->callbacks)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s"', $type));
}
$this->callbacks[$type][] = $callback;
return $this;
}
/**
* @param String $type
* @param callable $callback
*/
public function removeCallback($type, $callback) {
$pos = array_search($callback, $this->callbacks[$type]);
if($pos !== false) unset($this->callbacks[$type][$pos]);
return $this;
}
protected function invokeCallbacks($type, $args = array()) {
foreach($this->callbacks[$type] as $callback) {
call_user_func_array($callback, $args);
}
}
/**
* Parse a value from a fixture file. If it starts with =>
* it will get an ID from the fixture dictionary
*
* @param String $fieldVal
* @param Array $fixtures See {@link createObject()}
* @return String Fixture database ID, or the original value
*/
protected function parseValue($value, $fixtures = null) {
if(substr($value,0,2) == '=>') {
// Parse a dictionary reference - used to set foreign keys
list($class, $identifier) = explode('.', substr($value,2), 2);
if($fixtures && !isset($fixtures[$class][$identifier])) {
throw new InvalidArgumentException(sprintf(
'No fixture definitions found for "%s"',
$value
));
}
return $fixtures[$class][$identifier];
} else {
// Regular field value setting
return $value;
}
}
protected function setValue($obj, $name, $value, $fixtures = null) {
$obj->$name = $this->parseValue($value, $fixtures);
}
}

226
dev/FixtureFactory.php Normal file
View File

@ -0,0 +1,226 @@
<?php
/**
* Manages a set of database fixtures for {@link DataObject} records
* as well as raw database table rows.
*
* Delegates creation of objects to {@link FixtureBlueprint},
* which can implement class- and use-case specific fixture setup.
*
* Supports referencing model relations through a specialized syntax:
* <code>
* $factory = new FixtureFactory();
* $relatedObj = $factory->createObject(
* 'MyRelatedClass',
* 'relation1'
* );
* $obj = $factory->createObject(
* 'MyClass',
* 'object1'
* array('MyRelationName' => '=>MyRelatedClass.relation1')
* );
* </code>
* Relation loading is order dependant.
*
* @package framework
* @subpackage core
*/
class FixtureFactory {
/**
* @var array Array of fixture items, keyed by class and unique identifier,
* with values being the generated database ID. Does not store object instances.
*/
protected $fixtures = array();
/**
* @var array Callbacks
*/
protected $blueprints = array();
/**
* @param String $name Unique name for this blueprint
* @param array|FixtureBlueprint $defaults Array of default values, or a blueprint instance
*/
public function define($name, $defaults = array()) {
if($defaults instanceof FixtureBlueprint) {
$this->blueprints[$name] = $defaults;
} else {
$class = $name;
$this->blueprints[$name] = Injector::inst()->create(
'FixtureBlueprint', $name, $class, $defaults
);
}
return $this;
}
/**
* Writes the fixture into the database using DataObjects
*
* @param String $name Name of the {@link FixtureBlueprint} to use,
* usually a DataObject subclass.
* @param String $identifier Unique identifier for this fixture type
* @param Array $data Map of properties. Overrides default data.
* @return DataObject
*/
public function createObject($name, $identifier, $data = null) {
if(!isset($this->blueprints[$name])) {
$this->blueprints[$name] = new FixtureBlueprint($name);
}
$blueprint = $this->blueprints[$name];
$obj = $blueprint->createObject($identifier, $data, $this->fixtures);
$class = $blueprint->getClass();
if(!isset($this->fixtures[$class])) {
$this->fixtures[$class] = array();
}
$this->fixtures[$class][$identifier] = $obj->ID;
return $obj;
}
/**
* Writes the fixture into the database directly using a database manipulation.
* Does not use blueprints. Only supports tables with a primary key.
*
* @param String $table Existing database table name
* @param String $identifier Unique identifier for this fixture type
* @param Array $data Map of properties
* @return Int Database identifier
*/
public function createRaw($table, $identifier, $data) {
$manipulation = array($table => array("fields" => array(), "command" => "insert"));
foreach($data as $fieldName => $fieldVal) {
$manipulation[$table]["fields"][$fieldName] = "'" . $this->parseValue($fieldVal) . "'";
}
DB::manipulate($manipulation);
$id = DB::getGeneratedID($table);
$this->fixtures[$table][$identifier] = $id;
return $id;
}
/**
* Get the ID of an object from the fixture.
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function getId($class, $identifier) {
if(isset($this->fixtures[$class][$identifier])) {
return $this->fixtures[$class][$identifier];
} else {
return false;
}
}
/**
* Return all of the IDs in the fixture of a particular class name.
*
* @return A map of fixture-identifier => object-id
*/
public function getIds($class) {
if(isset($this->fixtures[$class])) {
return $this->fixtures[$class];
} else {
return false;
}
}
/**
* @param String
* @param String $identifier
* @param Int $databaseId
*/
public function setId($class, $identifier, $databaseId) {
$this->fixtures[$class][$identifier] = $databaseId;
return $this;
}
/**
* Get an object from the fixture.
*
* @param $class The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function get($class, $identifier) {
$id = $this->getId($class, $identifier);
if($id) return DataObject::get_by_id($class, $id);
}
/**
* @return Array Map of class names, containing a map of in-memory identifiers
* mapped to database identifiers.
*/
public function getFixtures() {
return $this->fixtures;
}
/**
* Remove all fixtures previously defined through {@link createObject()}
* or {@link createRaw()}, both from the internal fixture mapping and the database.
* If the $class argument is set, limit clearing to items of this class.
*
* @param String $class
*/
public function clear($limitToClass = null) {
$classes = ($limitToClass) ? array($limitToClass) : array_keys($this->fixtures);
foreach($classes as $class) {
$ids = $this->fixtures[$class];
foreach($ids as $id => $dbId) {
if(class_exists($class)) {
$class::get()->byId($dbId)->delete();
} else {
$table = $class;
DB::manipulate(array(
$table => array("fields" => array('ID' => $dbId),
"command" => "delete")
));
}
unset($this->fixtures[$class][$id]);
}
}
}
/**
* @return Array Of {@link FixtureBlueprint} instances
*/
public function getBlueprints() {
return $this->blueprints;
}
/**
* @param String $name
* @return FixtureBlueprint
*/
public function getBlueprint($name) {
return (isset($this->blueprints[$name])) ? $this->blueprints[$name] : false;
}
/**
* Parse a value from a fixture file. If it starts with =>
* it will get an ID from the fixture dictionary
*
* @param String $fieldVal
* @return String Fixture database ID, or the original value
*/
protected function parseValue($value) {
if(substr($value,0,2) == '=>') {
// Parse a dictionary reference - used to set foreign keys
list($class, $identifier) = explode('.', substr($value,2), 2);
if($this->fixtures && !isset($this->fixtures[$class][$identifier])) {
throw new InvalidArgumentException(sprintf(
'No fixture definitions found for "%s"',
$value
));
}
return $this->fixtures[$class][$identifier];
} else {
// Regular field value setting
return $value;
}
}
}

View File

@ -10,6 +10,11 @@ require_once 'TestRunner.php';
* @subpackage testing
*/
class SapphireTest extends PHPUnit_Framework_TestCase {
static $dependencies = array(
'fixtureFactory' => '%$FixtureFactory',
);
/**
* Path to fixture data for this test run.
* If passed as an array, multiple fixture files will be loaded.
@ -19,11 +24,14 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* @var string|array
*/
static $fixture_file = null;
/**
* @var FixtureFactory
*/
protected $fixtureFactory;
/**
* Set whether to include this test in the TestRunner or to skip this.
*
* @var bool
* @var bool Set whether to include this test in the TestRunner or to skip this.
*/
protected $skipTest = false;
@ -140,8 +148,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
/**
* @var array $fixtures Array of {@link YamlFixture} instances
* @deprecated 3.1 Use $fixtureFactory instad
*/
protected $fixtures;
protected $fixtures = array();
protected $model;
@ -234,8 +243,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
if($resolvedPath) $fixtureFilePath = $resolvedPath;
}
$fixture = new YamlFixture($fixtureFilePath);
$fixture->saveIntoDatabase($this->model);
$fixture = Injector::inst()->create('YamlFixture', $fixtureFilePath);
$fixture->writeInto($this->getFixtureFactory());
$this->fixtures[] = $fixture;
// backwards compatibility: Load first fixture into $this->fixture
@ -339,38 +348,37 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
}
/**
* Array
* @return FixtureFactory
*/
protected $fixtureDictionary;
public function getFixtureFactory() {
if(!$this->fixtureFactory) $this->fixtureFactory = Injector::inst()->create('FixtureFactory');
return $this->fixtureFactory;
}
public function setFixtureFactory(FixtureFactory $factory) {
$this->fixtureFactory = $factory;
return $this;
}
/**
* Get the ID of an object from the fixture.
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
* @return int
*/
protected function idFromFixture($className, $identifier) {
if(!$this->fixtures) {
user_error("You've called idFromFixture() but you haven't specified static \$fixture_file.\n",
E_USER_WARNING);
return;
}
foreach($this->fixtures as $fixture) {
$match = $fixture->idFromFixture($className, $identifier);
if($match) return $match;
$id = $this->getFixtureFactory()->getId($className, $identifier);
if(!$id) {
user_error(sprintf(
"Couldn't find object '%s' (class: %s)",
$identifier,
$className
), E_USER_ERROR);
}
$fixtureFiles = Config::inst()->get(get_class($this), 'fixture_file', Config::FIRST_SET);
user_error(sprintf(
"Couldn't find object '%s' (class: %s) in files %s",
$identifier,
$className,
(is_array($fixtureFiles)) ? implode(',', $fixtureFiles) : $fixtureFiles
), E_USER_ERROR);
return false;
return $id;
}
/**
@ -381,46 +389,27 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* @return A map of fixture-identifier => object-id
*/
protected function allFixtureIDs($className) {
if(!$this->fixtures) {
user_error("You've called allFixtureIDs() but you haven't specified static \$fixture_file.\n",
E_USER_WARNING);
return;
}
$ids = array();
foreach($this->fixtures as $fixture) {
$ids += $fixture->allFixtureIDs($className);
}
return $ids;
return $this->getFixtureFactory()->getIds($className);
}
/**
* Get an object from the fixture.
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
protected function objFromFixture($className, $identifier) {
if(!$this->fixtures) {
user_error("You've called objFromFixture() but you haven't specified static \$fixture_file.\n",
E_USER_WARNING);
return;
}
foreach($this->fixtures as $fixture) {
$match = $fixture->objFromFixture($className, $identifier);
if($match) return $match;
}
$obj = $this->getFixtureFactory()->get($className, $identifier);
$fixtureFiles = Config::inst()->get(get_class($this), 'fixture_file', Config::FIRST_SET);
user_error(sprintf(
"Couldn't find object '%s' (class: %s) in files %s",
$identifier,
$className,
(is_array($fixtureFiles)) ? implode(',', $fixtureFiles) : $fixtureFiles
), E_USER_ERROR);
if(!$obj) {
user_error(sprintf(
"Couldn't find object '%s' (class: %s)",
$identifier,
$className
), E_USER_ERROR);
}
return false;
return $obj;
}
/**
@ -431,20 +420,18 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* @param $fixtureFile The location of the .yml fixture file, relative to the site base dir
*/
public function loadFixture($fixtureFile) {
$parser = new Spyc();
$fixtureContent = $parser->load(Director::baseFolder().'/'.$fixtureFile);
$fixture = new YamlFixture($fixtureFile);
$fixture->saveIntoDatabase($this->model);
$fixture = Injector::inst()->create('YamlFixture', $fixtureFile);
$fixture->writeInto($this->getFixtureFactory());
$this->fixtures[] = $fixture;
}
/**
* Clear all fixtures which were previously loaded through
* {@link loadFixture()}.
* {@link loadFixture()}
*/
public function clearFixtures() {
$this->fixtures = array();
$this->getFixtureFactory()->clear();
}
/**
@ -765,7 +752,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
$dbConn->selectDatabase($dbname);
$dbConn->createDatabase();
$st = new SapphireTest();
$st = Injector::inst()->create('SapphireTest');
$st->resetDBSchema();
// Reinstate PHPUnit error handling

View File

@ -392,7 +392,7 @@ HTML;
// Fixture
if($fixtureFile) {
$fixture = new YamlFixture($fixtureFile);
$fixture = Injector::inst()->create('YamlFixture', $fixtureFile);
$fixture->saveIntoDatabase();
// If no fixture, then use defaults
@ -466,7 +466,7 @@ HTML;
SapphireTest::empty_temp_db();
if(isset($_GET['fixture']) && ($fixtureFile = $_GET['fixture'])) {
$fixture = new YamlFixture($fixtureFile);
$fixture = Injector::inst()->create('YamlFixture', $fixtureFile);
$fixture->saveIntoDatabase();
return "<p>Re-test the test database with fixture '$fixtureFile'. Time to start testing; where would"
. " you like to start?</p>";
@ -530,7 +530,7 @@ HTML;
}
// Fixture
$fixture = new YamlFixture($fixtureFile);
$fixture = Injector::inst()->create('YamlFixture', $fixtureFile);
$fixture->saveIntoDatabase();
return "<p>Loaded fixture '$fixtureFile' into session</p>";

View File

@ -66,11 +66,9 @@ require_once 'thirdparty/spyc/spyc.php';
* @subpackage core
*
* @see http://code.google.com/p/spyc/
*
* @todo Write unit test for YamlFixture
*/
class YamlFixture extends Object {
/**
* Absolute path to the .yml fixture file
*
@ -78,13 +76,6 @@ class YamlFixture extends Object {
*/
protected $fixtureFile;
/**
* Array of fixture items
*
* @var array
*/
protected $fixtureDictionary;
/**
* String containing fixture
*
@ -92,6 +83,12 @@ class YamlFixture extends Object {
*/
protected $fixtureString;
/**
* @var FixtureFactory
* @deprecated 3.1 Use writeInto() and FixtureFactory instead
*/
protected $factory;
/**
* @param String Absolute file path, or relative path to {@link Director::baseFolder()}
*/
@ -125,44 +122,50 @@ class YamlFixture extends Object {
public function getFixtureString() {
return $this->fixtureString;
}
/**
* Get the ID of an object from the fixture.
*
* @deprecated 3.1 Use writeInto() and FixtureFactory accessors instead
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function idFromFixture($className, $identifier) {
if(isset($this->fixtureDictionary[$className][$identifier])) {
return $this->fixtureDictionary[$className][$identifier];
} else {
return false;
}
Deprecation::notice('3.1', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->getId($className, $identifier);
}
/**
* Return all of the IDs in the fixture of a particular class name.
*
* @deprecated 3.1 Use writeInto() and FixtureFactory accessors instead
*
* @return A map of fixture-identifier => object-id
*/
public function allFixtureIDs($className) {
if(isset($this->fixtureDictionary[$className])) {
return $this->fixtureDictionary[$className];
} else {
return false;
}
Deprecation::notice('3.1', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->getIds($className);
}
/**
* Get an object from the fixture.
*
* @deprecated 3.1 Use writeInto() and FixtureFactory accessors instead
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function objFromFixture($className, $identifier) {
$id = $this->idFromFixture($className, $identifier);
if($id) return DataObject::get_by_id($className, $id);
Deprecation::notice('3.1', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->get($className, $identifier);
}
/**
@ -172,14 +175,24 @@ class YamlFixture extends Object {
* Caution: In order to support reflexive relations which need a valid object ID,
* the record is written twice: first after populating all non-relational fields,
* then again after populating all relations (has_one, has_many, many_many).
*
* @deprecated 3.1 Use writeInto() and FixtureFactory instance instead
*/
public function saveIntoDatabase(DataModel $model) {
// We have to disable validation while we import the fixtures, as the order in
// which they are imported doesnt guarantee valid relations until after the
// import is complete.
$validationenabled = DataObject::get_validation_enabled();
DataObject::set_validation_enabled(false);
Deprecation::notice('3.1', 'Use writeInto() and FixtureFactory instance instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
$this->writeInto($this->factory);
}
/**
* Persists the YAML data in a FixtureFactory,
* which in turn saves them into the database.
* Please use the passed in factory to access the fixtures afterwards.
*
* @param FixtureFactory $factory
*/
public function writeInto(FixtureFactory $factory) {
$parser = new Spyc();
if (isset($this->fixtureString)) {
$fixtureContent = $parser->load($this->fixtureString);
@ -187,112 +200,15 @@ class YamlFixture extends Object {
$fixtureContent = $parser->loadFile($this->fixtureFile);
}
$this->fixtureDictionary = array();
foreach($fixtureContent as $dataClass => $items) {
if(ClassInfo::exists($dataClass)) {
$this->writeDataObject($model, $dataClass, $items);
} else {
$this->writeSQL($dataClass, $items);
}
}
DataObject::set_validation_enabled($validationenabled);
}
/**
* Writes the fixture into the database using DataObjects
*
* @param string $dataClass
* @param array $items
*/
protected function writeDataObject($model, $dataClass, $items) {
foreach($items as $identifier => $fields) {
$obj = $model->$dataClass->newObject();
// If an ID is explicitly passed, then we'll sort out the initial write straight away
// This is just in case field setters triggered by the population code in the next block
// Call $this->write(). (For example, in FileTest)
if(isset($fields['ID'])) {
$obj->ID = $fields['ID'];
// The database needs to allow inserting values into the foreign key column (ID in our case)
$conn = DB::getConn();
if(method_exists($conn, 'allowPrimaryKeyEditing')) {
$conn->allowPrimaryKeyEditing(ClassInfo::baseDataClass($dataClass), true);
foreach($fixtureContent as $class => $items) {
foreach($items as $identifier => $data) {
if(ClassInfo::exists($class)) {
$factory->createObject($class, $identifier, $data);
} else {
$factory->createRaw($class, $identifier, $data);
}
$obj->write(false, true);
if(method_exists($conn, 'allowPrimaryKeyEditing')) {
$conn->allowPrimaryKeyEditing(ClassInfo::baseDataClass($dataClass), false);
}
}
// Populate the dictionary with the ID
if($fields) foreach($fields as $fieldName => $fieldVal) {
if($obj->many_many($fieldName) || $obj->has_many($fieldName) || $obj->has_one($fieldName)) continue;
$obj->$fieldName = $this->parseFixtureVal($fieldVal);
}
$obj->write();
// has to happen before relations in case a class is referring to itself
$this->fixtureDictionary[$dataClass][$identifier] = $obj->ID;
// Populate all relations
if($fields) foreach($fields as $fieldName => $fieldVal) {
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
$parsedItems = array();
$items = preg_split('/ *, */',trim($fieldVal));
foreach($items as $item) {
$parsedItems[] = $this->parseFixtureVal($item);
}
$obj->write();
if($obj->has_many($fieldName)) {
$obj->getComponents($fieldName)->setByIDList($parsedItems);
} elseif($obj->many_many($fieldName)) {
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
}
} elseif($obj->has_one($fieldName)) {
$obj->{$fieldName . 'ID'} = $this->parseFixtureVal($fieldVal);
}
}
$obj->write();
//If LastEdited was set in the fixture, set it here
if (array_key_exists('LastEdited', $fields)) {
$manip = array($dataClass => array("command" => "update", "id" => $obj->id,
"fields" => array("LastEdited" => "'".$this->parseFixtureVal($fields['LastEdited'])."'")));
DB::manipulate($manip);
}
}
}
/**
* Writes the fixture into the database directly using a database manipulation
*
* @param string $table
* @param array $items
*/
protected function writeSQL($table, $items) {
foreach($items as $identifier => $fields) {
$manipulation = array($table => array("fields" => array(), "command" => "insert"));
foreach($fields as $fieldName=> $fieldVal) {
$manipulation[$table]["fields"][$fieldName] = "'".$this->parseFixtureVal($fieldVal)."'";
}
DB::manipulate($manipulation);
$this->fixtureDictionary[$table][$identifier] = DB::getGeneratedID($table);
}
}
/**
* Parse a value from a fixture file. If it starts with => it will get an ID from the fixture dictionary
*/
protected function parseFixtureVal($fieldVal) {
// Parse a dictionary reference - used to set foreign keys
if(substr($fieldVal,0,2) == '=>') {
list($a, $b) = explode('.', substr($fieldVal,2), 2);
return $this->fixtureDictionary[$a][$b];
// Regular field value setting
} else {
return $fieldVal;
}
}
}

View File

@ -62,76 +62,8 @@ specific to the framework, e.g. `[assertEmailSent](api:SapphireTest->assertEmail
which can simulate sending emails through the `Email->send()` API without actually
using a mail server (see the [testing emails](email-sending)) guide.
## The Database YAML file
## Fixtures
The main feature of `[api:SapphireTest]` over the raw PHPUnit classes is that SapphireTest will prepare a temporary database for
you. The content of that database is provided in a special YAML file. YAML is a simple markup languages that uses tabs
and colons instead of the more verbose XML tags, and because of this much better for developers creating files by hand.
We will begin with a sample file and talk our way through it.
Page:
home:
Title: Home
about:
Title: About Us
staff:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
staffduplicate:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
products:
Title: Products
product1:
Title: 1.1 Test Product
product2:
Title: Another Product
product3:
Title: Another Product
product4:
Title: Another Product
contact:
Title: Contact Us
ErrorPage:
404:
Title: Page not Found
ErrorCode: 404
The contents of the YAML file are broken into three levels.
* **Top level: class names** - Page and ErrorPage. This is the name of the dataobject class that should be created.
The fact that ErrorPage is actually a subclass is irrelevant to the system populating the database. It just
instantiates the object you specify.
* **Second level: identifiers** - home, about, staff, staffduplicate, etc. These are the identifiers that you pass as
the second argument of SapphireTest::objFromFixture(). Each identifier you specify delimits a new database record.
This means that every record needs to have an identifier, whether you use it or not.
* **Third level: fields** - each field for the record is listed as a 3rd level entry. In most cases, the field's raw
content is provided. However, if you want to define a relationship, you can do so using "=>".
There are a couple of lines like this:
Parent: =>Page.about
This will tell the system to set the ParentID database field to the ID of the Page object with the identifier "about".
This can be used on any has-one or many-many relationship. Note that we use the name of the relationship (Parent), and
not the name of the database field (ParentID)
On many-many relationships, you should specify a comma separated list of values.
MyRelation: =>Class.inst1,=>Class.inst2,=>Class.inst3
An crucial thing to note is that **the YAML file specifies DataObjects, not database records**. The database is
populated by instantiating DataObject objects, setting the fields listed, and calling write(). This means that any
onBeforeWrite() or default value logic will be executed as part of the test. This forms the basis of our
testURLGeneration() test above.
For example, the URLSegment value of Page.staffduplicate is the same as the URLSegment value of Page.staff. When the
fixture is set up, the URLSegment value of Page.staffduplicate will actually be my-staff-2.
Finally, be aware that requireDefaultRecords() is **not** called by the database populator - so you will need to specify
standard pages such as 404 and home in your YAML file.
Often you need to test your functionality with some existing data, so called "fixtures".
These records are inserted on a fresh test database automatically.
[Read more about fixtures](fixtures).

View File

@ -0,0 +1,228 @@
# Fixtures
## Overview
Often you need to test your functionality with some existing data, so called "fixtures".
The `[api:SapphireTest]` class already prepares an empty database for you,
and you have various ways to define those fixtures.
## YAML Fixtures
YAML is a markup language which is deliberately simple and easy to read,
so ideal for our fixture generation.
We will begin with a sample file and talk our way through it.
Page:
home:
Title: Home
about:
Title: About Us
staff:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
RedirectorPage:
redirect_home:
RedirectionType: Internal
LinkTo: =>Page.home
The contents of the YAML file are broken into three levels.
* **Top level: class names** - `Page` and `RedirectorPage`. This is the name of the dataobject class that should be created.
The fact that `RedirectorPage` is actually a subclass is irrelevant to the system populating the database. It just
instantiates the object you specify.
* **Second level: identifiers** - `home`, `about`, etc. These are the identifiers that you pass as
the second argument of SapphireTest::objFromFixture(). Each identifier you specify delimits a new database record.
This means that every record needs to have an identifier, whether you use it or not.
* **Third level: fields** - each field for the record is listed as a 3rd level entry. In most cases, the field's raw
content is provided. However, if you want to define a relationship, you can do so using "=>".
There are a couple of lines like this:
Parent: =>Page.about
This will tell the system to set the ParentID database field to the ID of the Page object with the identifier "about".
This can be used on any has-one or many-many relationship. Note that we use the name of the relationship (Parent), and
not the name of the database field (ParentID)
On many-many relationships, you should specify a comma separated list of values.
MyRelation: =>Class.inst1,=>Class.inst2,=>Class.inst3
An crucial thing to note is that **the YAML file specifies DataObjects, not database records**. The database is
populated by instantiating DataObject objects, setting the fields listed, and calling write(). This means that any
onBeforeWrite() or default value logic will be executed as part of the test. This forms the basis of our
testURLGeneration() test above.
For example, the URLSegment value of Page.staffduplicate is the same as the URLSegment value of Page.staff. When the
fixture is set up, the URLSegment value of Page.staffduplicate will actually be my-staff-2.
Finally, be aware that requireDefaultRecords() is **not** called by the database populator - so you will need to specify
standard pages such as 404 and home in your YAML file.
## Test Class Definition
## Manual Object Creation
Sometimes statically defined fixtures don't suffice, because of the complexity of the tested model,
or because the YAML format doesn't allow you to modify all model state.
One common example here is publishing pages (page fixtures aren't published by default).
You can always resort to creating objects manually in the test setup phase.
Since the test database is cleared on every test method, you'll get a fresh
set of test instances every time.
:::php
class SiteTreeTest extends SapphireTest {
function setUp() {
parent::setUp();
for($i=0; $i<100; $i++) {
$page = new Page(array('Title' => "Page $i"));
$page->write();
$page->publish('Stage', 'Live');
}
}
}
## Fixture Factories
### Why Factories?
Manually defined fixture provide full flexibility, but very little in terms of structure and convention.
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you
to set default values, callbacks on object creation, and dynamic/lazy value setting.
By the way, the `SapphireTest` YAML fixtures rely on internally on this class as well.
The idea is that rather than instanciating objects directly, we'll have a factory class for them.
This factory can have so called "blueprints" defined on it, which tells the factory
how to instanciate an object of a specific type. Blueprints need a name,
which is usually set to the class it creates.
### Usage
Since blueprints are auto-created for all available DataObject subclasses,
you only need to instanciate a factory to start using it.
:::php
$factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('MyClass', 'myobj1');
It is important to remember that fixtures are referenced by arbitrary
identifiers ('myobj1'). These are internally mapped to their database identifiers.
:::
$databaseId = $factory->getId('MyClass', 'myobj1');
In order to create an object with certain properties, just add a second argument:
:::php
$obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value'));
### Default Properties
Blueprints can be overwritten in order to customize their behaviour,
for example with default properties in case none are passed into `createObject()`.
:::php
$factory->define('MyObject', array(
'MyProperty' => 'My Default Value'
));
### Dependent Properties
Values can be set on demand through anonymous functions,
which can either generate random defaults, or create
composite values based on other fixture data.
:::php
$factory->define('Member', array(
'Email' => function($obj, $data, $fixtures) {
if(isset($data['FirstName']) {
$obj->Email = strtolower($data['FirstName']) . '@example.org';
}
},
'Score' => function($obj, $data, $fixtures) {
$obj->Score = rand(0,10);
}
));
### Relations
Model relations can be expressed through the same notation as in the YAML fixture format
described earlier, through the `=>` prefix on data values.
:::php
$obj = $factory->createObject('MyObject', array(
'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2'
));
### Callbacks
Sometimes new model instances need to be modified in ways which can't be expressed
in their properties, for example to publish a page, which requires a method call.
:::php
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
$obj->publish('Stage', 'Live');
});
$page = $factory->define('Page', $blueprint);
Available callbacks:
* `beforeCreate($identifier, $data, $fixtures)`
* `afterCreate($obj, $identifier, $data, $fixtures)`
### Multiple Blueprints
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 equal the class names they manage.
:::php
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
if(isset($fixtures['Group']['admin'])) {
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
$obj->Groups()->add($adminGroup);
}
});
$member = $factory->createObject('Member'); // not in admin group
$admin = $factory->createObject('AdminMember'); // in admin group
### Full Test Example
:::php
class MyObjectTest extends SapphireTest {
protected $factory;
function __construct() {
parent::__construct();
$factory = Injector::inst()->create('FixtureFactory');
// Defines a "blueprint" for new objects
$factory->define('MyObject', array(
'MyProperty' => 'My Default Value'
));
$this->factory = $factory;
}
function testSomething() {
$MyObjectObj = $this->factory->createObject(
'MyObject',
array('MyOtherProperty' => 'My Custom Value')
);
// $myPageObj->MyProperty = My Default Value
// $myPageObj->MyOtherProperty = My Custom Value
}
}

View File

@ -0,0 +1,181 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class FixtureBlueprintTest extends SapphireTest {
protected $usesDatabase = true;
protected $extraDataObjects = array(
'FixtureFactoryTest_DataObject',
'FixtureFactoryTest_DataObjectRelation'
);
public function testCreateWithoutData() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject('one');
$this->assertNotNull($obj);
$this->assertGreaterThan(0, $obj->ID);
$this->assertEquals('', $obj->Name);
}
public function testCreateWithData() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject('one', array('Name' => 'My Name'));
$this->assertNotNull($obj);
$this->assertGreaterThan(0, $obj->ID);
$this->assertEquals('My Name', $obj->Name);
}
public function testCreateWithRelationship() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$relation1 = new FixtureFactoryTest_DataObjectRelation();
$relation1->write();
$relation2 = new FixtureFactoryTest_DataObjectRelation();
$relation2->write();
$obj = $blueprint->createObject(
'one',
array(
'ManyMany' =>
'=>FixtureFactoryTest_DataObjectRelation.relation1,' .
'=>FixtureFactoryTest_DataObjectRelation.relation2'
),
array(
'FixtureFactoryTest_DataObjectRelation' => array(
'relation1' => $relation1->ID,
'relation2' => $relation2->ID
)
)
);
$this->assertEquals(2, $obj->ManyMany()->Count());
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage No fixture definitions found
*/
public function testCreateWithInvalidRelationName() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject(
'one',
array(
'ManyMany' => '=>UnknownClass.relation1'
),
array(
'FixtureFactoryTest_DataObjectRelation' => array(
'relation1' => 99
)
)
);
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage No fixture definitions found
*/
public function testCreateWithInvalidRelationIdentifier() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject(
'one',
array(
'ManyMany' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
),
array(
'FixtureFactoryTest_DataObjectRelation' => array(
'relation1' => 99
)
)
);
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Invalid format
*/
public function testCreateWithInvalidRelationFormat() {
$factory = new FixtureFactory();
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$relation1 = new FixtureFactoryTest_DataObjectRelation();
$relation1->write();
$obj = $blueprint->createObject(
'one',
array(
'ManyMany' => 'FixtureFactoryTest_DataObjectRelation.relation1'
),
array(
'FixtureFactoryTest_DataObjectRelation' => array(
'relation1' => $relation1->ID
)
)
);
}
public function testCreateWithId() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject('ninetynine', array('ID' => 99));
$this->assertNotNull($obj);
$this->assertEquals(99, $obj->ID);
}
function testCallbackOnBeforeCreate() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$this->_called = 0;
$self = $this;
$cb = function($identifier, $data, $fixtures) use($self) {
$self->_called = $self->_called + 1;
};
$blueprint->addCallback('beforeCreate', $cb);
$this->assertEquals(0, $this->_called);
$obj1 = $blueprint->createObject('one');
$this->assertEquals(1, $this->_called);
$obj2 = $blueprint->createObject('two');
$this->assertEquals(2, $this->_called);
$this->_called = 0;
}
function testCallbackOnAfterCreate() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$this->_called = 0;
$self = $this;
$cb = function($obj, $identifier, $data, $fixtures) use($self) {
$self->_called = $self->_called + 1;
};
$blueprint->addCallback('afterCreate', $cb);
$this->assertEquals(0, $this->_called);
$obj1 = $blueprint->createObject('one');
$this->assertEquals(1, $this->_called);
$obj2 = $blueprint->createObject('two');
$this->assertEquals(2, $this->_called);
$this->_called = 0;
}
function testDefineWithDefaultCustomSetters() {
$blueprint = new FixtureBlueprint(
'FixtureFactoryTest_DataObject',
null,
array(
'Name' => function($obj, $data, $fixtures) {
return 'Default Name';
}
));
$obj1 = $blueprint->createObject('one');
$this->assertEquals('Default Name', $obj1->Name);
$obj2 = $blueprint->createObject('one', array('Name' => 'Override Name'));
$this->assertEquals('Override Name', $obj2->Name);
}
}

View File

@ -0,0 +1,170 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class FixtureFactoryTest extends SapphireTest {
protected $usesDatabase = true;
protected $extraDataObjects = array(
'FixtureFactoryTest_DataObject',
'FixtureFactoryTest_DataObjectRelation'
);
public function testCreateRaw() {
$factory = new FixtureFactory();
$id = $factory->createRaw('FixtureFactoryTest_DataObject', 'one', array('Name' => 'My Name'));
$this->assertNotNull($id);
$this->assertGreaterThan(0, $id);
$obj = FixtureFactoryTest_DataObject::get()->find('ID', $id);
$this->assertNotNull($obj);
$this->assertEquals('My Name', $obj->Name);
}
public function testSetId() {
$factory = new FixtureFactory();
$obj = new FixtureFactoryTest_DataObject();
$obj->write();
$factory->setId('FixtureFactoryTest_DataObject', 'one', $obj->ID);
$this->assertEquals(
$obj->ID,
$factory->getId('FixtureFactoryTest_DataObject', 'one')
);
}
public function testGetId() {
$factory = new FixtureFactory();
$obj = $factory->createObject('FixtureFactoryTest_DataObject', 'one');
$this->assertEquals(
$obj->ID,
$factory->getId('FixtureFactoryTest_DataObject', 'one')
);
}
public function testGetIds() {
$factory = new FixtureFactory();
$obj = $factory->createObject('FixtureFactoryTest_DataObject', 'one');
$this->assertEquals(
array('one' => $obj->ID),
$factory->getIds('FixtureFactoryTest_DataObject')
);
}
public function testDefine() {
$factory = new FixtureFactory();
$this->assertFalse($factory->getBlueprint('FixtureFactoryTest_DataObject'));
$factory->define('FixtureFactoryTest_DataObject');
$this->assertInstanceOf(
'FixtureBluePrint',
$factory->getBlueprint('FixtureFactoryTest_DataObject')
);
}
public function testDefineWithCustomBlueprint() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$factory = new FixtureFactory();
$this->assertFalse($factory->getBlueprint('FixtureFactoryTest_DataObject'));
$factory->define('FixtureFactoryTest_DataObject', $blueprint);
$this->assertInstanceOf(
'FixtureBluePrint',
$factory->getBlueprint('FixtureFactoryTest_DataObject')
);
$this->assertEquals(
$blueprint,
$factory->getBlueprint('FixtureFactoryTest_DataObject')
);
}
public function testDefineWithDefaults() {
$factory = new FixtureFactory();
$factory->define('FixtureFactoryTest_DataObject', array('Name' => 'Default'));
$obj = $factory->createObject('FixtureFactoryTest_DataObject', 'one');
$this->assertEquals('Default', $obj->Name);
}
public function testDefineMultipleBlueprintsForClass() {
$factory = new FixtureFactory();
$factory->define(
'FixtureFactoryTest_DataObject',
new FixtureBlueprint('FixtureFactoryTest_DataObject')
);
$factory->define(
'FixtureFactoryTest_DataObjectWithDefaults',
new FixtureBlueprint(
'FixtureFactoryTest_DataObjectWithDefaults',
'FixtureFactoryTest_DataObject',
array('Name' => 'Default')
)
);
$obj = $factory->createObject('FixtureFactoryTest_DataObject', 'one');
$this->assertNull($obj->Name);
$objWithDefaults = $factory->createObject('FixtureFactoryTest_DataObjectWithDefaults', 'two');
$this->assertEquals('Default', $objWithDefaults->Name);
$this->assertEquals(
$obj->ID,
$factory->getId('FixtureFactoryTest_DataObject', 'one')
);
$this->assertEquals(
$objWithDefaults->ID,
$factory->getId('FixtureFactoryTest_DataObject', 'two'),
'Can access fixtures under class name, not blueprint name'
);
}
public function testClear() {
$factory = new FixtureFactory();
$obj1Id = $factory->createRaw('FixtureFactoryTest_DataObject', 'one', array('Name' => 'My Name'));
$obj2 = $factory->createObject('FixtureFactoryTest_DataObject', 'two');
$factory->clear();
$this->assertFalse($factory->getId('FixtureFactoryTest_DataObject', 'one'));
$this->assertNull(FixtureFactoryTest_DataObject::get()->byId($obj1Id));
$this->assertFalse($factory->getId('FixtureFactoryTest_DataObject', 'two'));
$this->assertNull(FixtureFactoryTest_DataObject::get()->byId($obj2->ID));
}
public function testClearWithClass() {
$factory = new FixtureFactory();
$obj1 = $factory->createObject('FixtureFactoryTest_DataObject', 'object-one');
$relation1 = $factory->createObject('FixtureFactoryTest_DataObjectRelation', 'relation-one');
$factory->clear('FixtureFactoryTest_DataObject');
$this->assertFalse(
$factory->getId('FixtureFactoryTest_DataObject', 'one')
);
$this->assertNull(FixtureFactoryTest_DataObject::get()->byId($obj1->ID));
$this->assertEquals(
$relation1->ID,
$factory->getId('FixtureFactoryTest_DataObjectRelation', 'relation-one')
);
$this->assertInstanceOf(
'FixtureFactoryTest_DataObjectRelation',
FixtureFactoryTest_DataObjectRelation::get()->byId($relation1->ID)
);
}
}
class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
static $db = array(
"Name" => "Varchar"
);
static $many_many = array(
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
);
}
class FixtureFactoryTest_DataObjectRelation extends DataObject implements TestOnly {
static $db = array(
"Name" => "Varchar"
);
static $belongs_many_many = array(
"TestParent" => "FixtureFactoryTest_DataObject"
);
}

View File

@ -259,8 +259,8 @@ class DataObjectTest extends SapphireTest {
$team->Comments()->add($newComment);
$this->assertEquals($team->ID, $newComment->TeamID);
$comment1 = $this->fixture->objFromFixture('DataObjectTest_TeamComment', 'comment1');
$comment2 = $this->fixture->objFromFixture('DataObjectTest_TeamComment', 'comment2');
$comment1 = $this->objFromFixture('DataObjectTest_TeamComment', 'comment1');
$comment2 = $this->objFromFixture('DataObjectTest_TeamComment', 'comment2');
$team->Comments()->remove($comment2);
$commentIDs = $team->Comments()->sort('ID')->column('ID');

View File

@ -2,8 +2,6 @@
class YamlFixtureTest extends SapphireTest {
static $fixture_file = 'YamlFixtureTest.yml';
protected $extraDataObjects = array(
'YamlFixtureTest_DataObject',
'YamlFixtureTest_DataObjectRelation',
@ -11,14 +9,14 @@ class YamlFixtureTest extends SapphireTest {
public function testAbsoluteFixturePath() {
$absPath = FRAMEWORK_PATH . '/tests/testing/YamlFixtureTest.yml';
$obj = new YamlFixture($absPath);
$obj = Injector::inst()->create('YamlFixture', $absPath);
$this->assertEquals($absPath, $obj->getFixtureFile());
$this->assertNull($obj->getFixtureString());
}
public function testRelativeFixturePath() {
$relPath = FRAMEWORK_DIR . '/tests/testing/YamlFixtureTest.yml';
$obj = new YamlFixture($relPath);
$obj = Injector::inst()->create('YamlFixture', $relPath);
$this->assertEquals(Director::baseFolder() . '/' . $relPath, $obj->getFixtureFile());
$this->assertNull($obj->getFixtureString());
}
@ -26,7 +24,7 @@ class YamlFixtureTest extends SapphireTest {
public function testStringFixture() {
$absPath = FRAMEWORK_PATH . '/tests/testing/YamlFixtureTest.yml';
$string = file_get_contents($absPath);
$obj = new YamlFixture($string);
$obj = Injector::inst()->create('YamlFixture', $string);
$this->assertEquals($string, $obj->getFixtureString());
$this->assertNull($obj->getFixtureFile());
}
@ -36,16 +34,42 @@ class YamlFixtureTest extends SapphireTest {
*/
public function testFailsWithInvalidFixturePath() {
$invalidPath = FRAMEWORK_DIR . '/tests/testing/invalid.yml';
$obj = new YamlFixture($invalidPath);
$obj = Injector::inst()->create('YamlFixture', $invalidPath);
}
public function testSQLInsert() {
$object1 = DataObject::get_by_id("YamlFixtureTest_DataObject",
$this->idFromFixture("YamlFixtureTest_DataObject", "testobject1"));
$this->assertTrue($object1->ManyMany()->Count() == 2, "Should be 2 items in this manymany relationship");
$object2 = DataObject::get_by_id("YamlFixtureTest_DataObject",
$this->idFromFixture("YamlFixtureTest_DataObject", "testobject2"));
$this->assertTrue($object2->ManyMany()->Count() == 2, "Should be 2 items in this manymany relationship");
$relPath = FRAMEWORK_DIR . '/tests/testing/YamlFixtureTest.yml';
$fixture = Injector::inst()->create('YamlFixture', $relPath);
$fixture->saveIntoDatabase(DataModel::inst());
$this->assertGreaterThan(0, $fixture->idFromFixture("YamlFixtureTest_DataObject", "testobject1"));
$object1 = DataObject::get_by_id(
"YamlFixtureTest_DataObject",
$fixture->idFromFixture("YamlFixtureTest_DataObject", "testobject1")
);
$this->assertTrue(
$object1->ManyMany()->Count() == 2,
"Should be two items in this relationship"
);
$this->assertGreaterThan(0, $fixture->idFromFixture("YamlFixtureTest_DataObject", "testobject2"));
$object2 = DataObject::get_by_id(
"YamlFixtureTest_DataObject",
$fixture->idFromFixture("YamlFixtureTest_DataObject", "testobject2")
);
$this->assertTrue(
$object2->ManyMany()->Count() == 1,
"Should be one item in this relationship"
);
}
public function testWriteInto() {
$factory = Injector::inst()->create('FixtureFactory');
$relPath = FRAMEWORK_DIR . '/tests/testing/YamlFixtureTest.yml';
$fixture = Injector::inst()->create('YamlFixture', $relPath);
$fixture->writeInto($factory);
$this->assertGreaterThan(0, $factory->getId("YamlFixtureTest_DataObject", "testobject1"));
}
}

View File

@ -1,23 +1,12 @@
YamlFixtureTest_DataObject:
testobject1:
Name: TestObject1
testobject2:
Name: TestObject2
YamlFixtureTest_DataObjectRelation:
relation1:
Name: Relation1
relation2:
Name: Relation2
YamlFixtureTest_DataObject_ManyMany:
manymany1:
YamlFixtureTest_DataObjectID: =>YamlFixtureTest_DataObject.testobject1
YamlFixtureTest_DataObjectRelationID: =>YamlFixtureTest_DataObjectRelation.relation1
manymany2:
YamlFixtureTest_DataObjectID: =>YamlFixtureTest_DataObject.testobject1
YamlFixtureTest_DataObjectRelationID: =>YamlFixtureTest_DataObjectRelation.relation2
manymany3:
YamlFixtureTest_DataObjectID: =>YamlFixtureTest_DataObject.testobject2
YamlFixtureTest_DataObjectRelationID: =>YamlFixtureTest_DataObjectRelation.relation1
manymany4:
YamlFixtureTest_DataObjectID: =>YamlFixtureTest_DataObject.testobject2
YamlFixtureTest_DataObjectRelationID: =>YamlFixtureTest_DataObjectRelation.relation2
YamlFixtureTest_DataObject:
testobject1:
Name: TestObject1
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
testobject2:
Name: TestObject2
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1