diff --git a/Lib/Definition/FabricateDefinition.php b/Lib/Definition/FabricateDefinition.php new file mode 100644 index 0000000..56f5a66 --- /dev/null +++ b/Lib/Definition/FabricateDefinition.php @@ -0,0 +1,27 @@ +define = $define; + } + + public function run($data, $world) { + $result = []; + if(is_callable($this->define)) { + $callback = $this->define; + $result = $callback($data, $world); + } else if(is_array($this->define)) { + $result = $this->define; + } + return $result; + } +} \ No newline at end of file diff --git a/Lib/Fabricate.php b/Lib/Fabricate.php index 76908d8..551a08e 100755 --- a/Lib/Fabricate.php +++ b/Lib/Fabricate.php @@ -1,6 +1,9 @@ config = new FabricateConfig(); + self::$_instance->registry = new FabricateRegistry('Fabricate'); + self::$_instance->traits = []; } return self::$_instance; } @@ -24,8 +33,11 @@ private static function getInstance() { * Override constructor. */ public function __construct() { - $this->config = new FabricateConfig(); - } + } + + public static function clear() { + self::$_instance = null; + } /** * To override these settings @@ -43,11 +55,10 @@ public static function config($callback) { */ public static function create($modelName, $recordCount=1, $callback = null) { $attributes = self::attributes_for($modelName, $recordCount, $callback); - $model = ClassRegistry::init($modelName); - foreach ($attributes as $data) { - $model->create($data); - $model->save(null, self::getInstance()->config->auto_validate); - } + $instance = self::getInstance(); + $definition = $instance->definition($recordCount, $callback); + $recordCount = $instance->recordCount($recordCount); + return $instance->factory->create($attributes, $recordCount, $definition); } /** * Only create a model instance. @@ -57,97 +68,103 @@ public static function create($modelName, $recordCount=1, $callback = null) { */ public static function build($modelName, $callback = null) { $data = self::attributes_for($modelName, 1, $callback); - $model = ClassRegistry::init($modelName); - $model->create($data[0]); - return $model; + $instance = self::getInstance(); + $definition = $instance->definition(1, $callback); + return $instance->factory->build($data, $definition); } /** * Only create model attributes array. * @return array model attributes array. */ public static function attributes_for($modelName, $recordCount=1, $callback = null) { - if(is_callable($recordCount) || is_array($recordCount)) { - $callback = $recordCount; - $recordCount = 1; - } - $model = ClassRegistry::init($modelName); - $results = self::getInstance()->_generateRecords($model->schema(), $recordCount, $callback); - return $results; + $instance = self::getInstance(); + $instance->factory = $instance->factory($modelName); + $definition = $instance->definition($recordCount, $callback); + $recordCount = $instance->recordCount($recordCount); + return $instance->factory->attributes_for($recordCount, $definition); } /** - * Generate String representation of Records - * - * @param array $tableInfo Table schema array - * @param integer $recordCount - * @return array Array of records. + * Only create model attributes array for association. + * @return array model attributes array. */ - private function _generateRecords($tableInfo, $recordCount = 1, $callback) { - $world = new FabricateContext($this->config); - $records = array(); - for ($i = 0; $i < $recordCount; $i++) { - $record = array(); - foreach ($tableInfo as $field => $fieldInfo) { - if (empty($fieldInfo['type'])) { - continue; - } - $insert = ''; - switch ($fieldInfo['type']) { - case 'integer': - case 'float': - $insert = $this->config->sequence_start + $i; - break; - case 'string': - case 'binary': - $isPrimaryUuid = ( - isset($fieldInfo['key']) && strtolower($fieldInfo['key']) === 'primary' && - isset($fieldInfo['length']) && $fieldInfo['length'] == 36 - ); - if ($isPrimaryUuid) { - $insert = String::uuid(); - } else { - $insert = "Lorem ipsum dolor sit amet"; - if (!empty($fieldInfo['length'])) { - $insert = substr($insert, 0, (int)$fieldInfo['length'] - 2); - } - } - break; - case 'timestamp': - $insert = time(); - break; - case 'datetime': - $insert = date('Y-m-d H:i:s'); - break; - case 'date': - $insert = date('Y-m-d'); - break; - case 'time': - $insert = date('H:i:s'); - break; - case 'boolean': - $insert = 1; - break; - case 'text': - $insert = "Lorem ipsum dolor sit amet, aliquet feugiat."; - $insert .= " Convallis morbi fringilla gravida,"; - $insert .= " phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin"; - $insert .= " venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla"; - $insert .= " vestibulum massa neque ut et, id hendrerit sit,"; - $insert .= " feugiat in taciti enim proin nibh, tempor dignissim, rhoncus"; - $insert .= " duis vestibulum nunc mattis convallis."; - break; - } - $record[$field] = $insert; - } - if(is_callable($callback)) { - $record = array_merge($record, $callback($record, $world)); - } else if(is_array($callback)) { - $record = array_merge($record, $callback); + public static function association($modelName, $recordCount=1, $callback = null) { + $instance = self::getInstance(); + $factory = $instance->factory($modelName); + $definition = $instance->definition($recordCount, $callback); + $recordCount = $instance->recordCount($recordCount); + return $factory->attributes_for($recordCount, $definition); + } + + /** + * Return defined traits. + * @return array registed trait definition + */ + public static function traits() { + return self::getInstance()->traits; + } + + /** + * @param mixed $name + * @param mixed $define + */ + public static function define($name, $define) { + $instance = self::getInstance(); + $parent = false; + $base = false; + $trait = false; + if(is_array($name)) { + $parent = array_key_exists('parent', $name)?$name['parent']:false; + $base = array_key_exists('class', $name)?$name['class']:false; + if(array_key_exists('trait', $name)) { + $name = $name['trait']; + $parent = $base = false; + $trait = true; + } else { + $name = $name[0]; } - $records[] = $record; } - return $records; + if(empty($name)) { + throw new InvalidArgumentException("name is empty"); + } + if($parent && !$instance->registry->is_registered($parent)) { + throw new InvalidArgumentException("parent `{$parent}` is not registered"); + } + if($base && in_array(ClassRegistry::init($base, true),[false, null])) { + throw new InvalidArgumentException("class `{$base}` is not found"); + } + $definition = new FabricateDefinition($define); + if($trait) { + $instance->traits[$name] = $definition; + return; + } + if(!$parent && !$base) { + $base = $name; + } + $definition->parent = $parent?FabricateFactory::create($instance->registry->find($parent)):false; + $definition->parent = $base?FabricateFactory::create(ClassRegistry::init($base)):$definition->parent; + $definition->parent->setConfig(self::getInstance()->config); + + $instance->registry->register($name, $definition); } + private function factory($name) { + $factory = FabricateFactory::create(self::getInstance()->registry->find($name)); + $factory->setConfig(self::getInstance()->config); + return $factory; + } + private function recordCount($recordCount) { + if(is_callable($recordCount) || is_array($recordCount)) { + $recordCount = 1; + } + return $recordCount; + } + + private function definition($recordCount, $callback) { + if(is_callable($recordCount) || is_array($recordCount)) { + $callback = $recordCount; + } + return new FabricateDefinition($callback); + } } \ No newline at end of file diff --git a/Lib/FabricateConfig.php b/Lib/FabricateConfig.php index 953e929..a45482a 100644 --- a/Lib/FabricateConfig.php +++ b/Lib/FabricateConfig.php @@ -6,4 +6,5 @@ class FabricateConfig { public $sequence_start = 1; public $auto_validate = false; + public $filter_key = false; } \ No newline at end of file diff --git a/Lib/FabricateContext.php b/Lib/FabricateContext.php index a9126de..12eda79 100644 --- a/Lib/FabricateContext.php +++ b/Lib/FabricateContext.php @@ -11,6 +11,10 @@ class FabricateContext { * Sequence Hash map */ private $sequences = []; + /** + * Trait use array + */ + private $traits = []; /** * Fabricate config */ @@ -50,4 +54,27 @@ public function sequence($name, $start=null, $callback=null) { $this->sequences[$name]->next(); return $ret; } + + /** + * Add apply trait in the scope. + * @param string|array $name use trait name(s) + */ + public function traits($name) { + if(is_array($name)) { + $this->traits = array_merge($this->traits, $name); + } else { + $this->traits[] = $name; + } + } + + /** + * Flush trait stack in the scope + * @return array flushed trait stack + */ + public function flashTraits() { + $traits = $this->traits; + $this->traits = []; + return $traits; + } + } \ No newline at end of file diff --git a/Lib/FabricateRegistry.php b/Lib/FabricateRegistry.php new file mode 100644 index 0000000..7a89c87 --- /dev/null +++ b/Lib/FabricateRegistry.php @@ -0,0 +1,59 @@ +name = $name; + $this->items = []; + } + + /** + * Clear registerd entries + */ + public function clear() { + $this->items = []; + } + + /** + * Find from registred or model by name + * @param string $name + * @return mixed registerd object + */ + public function find($name) { + if($this->is_registered($name)) { + return $this->items[$name]; + } + $model = ClassRegistry::init($name, true); + if($model) { + return $model; + } + throw new InvalidArgumentException("{$name} not registered"); + } + + /** + * Regist to registries + * @param string $name + * @param FabricateDefinition $item + */ + public function register($name, $item) { + $this->items[$name] = $item; + } + + /** + * Is registered? + * @param string $name + * @return boolean + */ + public function is_registered($name) { + return array_key_exists($name, $this->items); + } +} diff --git a/Lib/Factory/FabricateAbstractFactory.php b/Lib/Factory/FabricateAbstractFactory.php new file mode 100644 index 0000000..a191a96 --- /dev/null +++ b/Lib/Factory/FabricateAbstractFactory.php @@ -0,0 +1,80 @@ +config = $config; + } + + /** + * Generate Records + * + * @param array $params fakeRecord parameter + * @param integer $recordCount + * @param mixed $definition FabricateDefinition(s) + * @return array Array of records. + */ + protected function _generateRecords($params, $recordCount, $definitions) { + $world = new FabricateContext($this->config); + if(!is_array($definitions)) { + $definitions = [$definitions]; + } + $records = array(); + for ($i = 0; $i < $recordCount; $i++) { + $record = $this->fakeRecord($params, $i); + $records[] = $this->applyNestedDefinitions($definitions, $record, $world); + } + return $records; + } + + private function applyNestedDefinitions($definitions, $record, $world) { + foreach ($definitions as $definition) { + $result = $definition->run($record, $world); + $record = $this->applyTraits($record, $world); + $record = array_merge($record, $result); + } + return $record; + } + + private function applyTraits($record, $world) { + foreach ($world->flashTraits() as $use) { + $traits = Fabricate::traits(); + if(array_key_exists($use, $traits)) { + $record = array_merge($record, $traits[$use]->run($record, $world)); + } + } + return $record; + } + +} \ No newline at end of file diff --git a/Lib/Factory/FabricateDefinitionFactory.php b/Lib/Factory/FabricateDefinitionFactory.php new file mode 100644 index 0000000..3002a17 --- /dev/null +++ b/Lib/Factory/FabricateDefinitionFactory.php @@ -0,0 +1,38 @@ +definition = $definition; + } + + public function create($attributes, $recordCount, $definition) { + if($this->definition->parent) { + return $this->definition->parent->create($attributes, $recordCount, $definition); + } + return; + } + public function build($data, $definition) { + if($this->definition->parent) { + return $this->definition->parent->build($attributes, $definition); + } + return; + } + public function attributes_for($recordCount, $definition) { + if($this->definition->parent) { + if(is_array($definition)) { + $definitions = $definition; + } else { + $definitions = [$definition]; + } + array_unshift($definitions, $this->definition); + return $this->definition->parent->attributes_for($recordCount, $definitions); + } + return $this->_generateRecords([], $recordCount, $definition); + } + + protected function fakeRecord($params, $index) { + return []; + } +} \ No newline at end of file diff --git a/Lib/Factory/FabricateFactory.php b/Lib/Factory/FabricateFactory.php new file mode 100644 index 0000000..07af414 --- /dev/null +++ b/Lib/Factory/FabricateFactory.php @@ -0,0 +1,25 @@ +model = $model; + } + + public function create($attributes, $recordCount, $definition) { + foreach ($attributes as $data) { + $this->model->create($data, $this->config->filter_key); + $this->model->saveAssociated(null, [ + 'validate' => $this->config->auto_validate, + 'deep' => true, + ]); + } + return $this->model; + } + public function build($data, $definition) { + $this->model->create($data[0], $this->config->filter_key); + return $this->model; + } + public function attributes_for($recordCount, $definition) { + return $this->_generateRecords($this->model->schema(), $recordCount, $definition); + } + + protected function fakeRecord($tableInfo, $index) { + foreach ($tableInfo as $field => $fieldInfo) { + if (empty($fieldInfo['type'])) { + continue; + } + $insert = ''; + switch ($fieldInfo['type']) { + case 'integer': + case 'float': + $insert = $this->config->sequence_start + $index; + break; + case 'string': + case 'binary': + $isPrimaryUuid = ( + isset($fieldInfo['key']) && strtolower($fieldInfo['key']) === 'primary' && + isset($fieldInfo['length']) && $fieldInfo['length'] == 36 + ); + if ($isPrimaryUuid) { + $insert = String::uuid(); + } else { + $insert = "Lorem ipsum dolor sit amet"; + if (!empty($fieldInfo['length'])) { + $insert = substr($insert, 0, (int)$fieldInfo['length'] - 2); + } + } + break; + case 'timestamp': + $insert = time(); + break; + case 'datetime': + $insert = date('Y-m-d H:i:s'); + break; + case 'date': + $insert = date('Y-m-d'); + break; + case 'time': + $insert = date('H:i:s'); + break; + case 'boolean': + $insert = 1; + break; + case 'text': + $insert = "Lorem ipsum dolor sit amet, aliquet feugiat."; + $insert .= " Convallis morbi fringilla gravida,"; + $insert .= " phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin"; + $insert .= " venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla"; + $insert .= " vestibulum massa neque ut et, id hendrerit sit,"; + $insert .= " feugiat in taciti enim proin nibh, tempor dignissim, rhoncus"; + $insert .= " duis vestibulum nunc mattis convallis."; + break; + } + $record[$field] = $insert; + } + return $record; + } +} \ No newline at end of file diff --git a/Lib/empty b/Lib/empty deleted file mode 100755 index e69de29..0000000 diff --git a/README.md b/README.md index a478b48..20afc2c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Fabricate CakePHP data generator for Testing -It's inspired on [Fabrication](https://github.com/paulelliott/fabrication) from the Ruby world. +It's inspired on [Fabrication](https://github.com/paulelliott/fabrication) and [factory-girl](https://github.com/thoughtbot/factory_girl) from the Ruby world. Fabricate is a simple fake object generation plugin for CakePHP. Quickly Fabricate objects as needed anywhere in your app or test case. @@ -18,34 +18,83 @@ Add require-dev in your composer.json Add bootstrap -`CakePlugin::load('Fabricate');` +```php +CakePlugin::load('Fabricate'); +``` ## Usage ### The Basics +Include Fabricate class using App::uses on your test file + +```php +App::uses('Fabricate', 'Fabricate.Lib'); +``` + The simplest way to generate objects - Fabricate::create('Post') +```php +Fabricate::create('Post') +``` That will generate and save to database an instance of Post using the schema information. To set additional attributes or override what is in the Fabricator, you can pass a array to Fabricate with the fields you want to set. - Fabricate::create('Post', ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]) +```php +Fabricate::create('Post', ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]) +``` ### Fabricating With Blocks In addition to the array, you can pass a callback function to Fabricate and all the features of a Fabricator definition are available to you at object generation time. - Fabricate::create('Post', 10, function($data){ - return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; - }); +```php +Fabricate::create('Post', 10, function($data){ + return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; +}); +``` The hash will overwrite any fields defined in the callback function. ## APIs +### Configuration + +To override these settings, put a bootstrap.php in your app folder and append the path to phpunit.xml + +``` +Fabricate::config(function($config) { + $config->sequence_start = 1; + $config->auto_validate = false; + $config->filter_key = false; +}); +``` + +#### Supported Options + +##### sequence_start + +Allows you to specify the default starting number for all sequences. +This can still be overridden for specific sequences. + +`Default: 1` + +##### auto_validate + +Indicates whether or not to validate before creating. +see: CakePHP's Model::save() + +`Default: false` + +##### filter_key + +filter_key If true, overwrites any primary key input with an empty value. +see: CakePHP's Model::create() + +`Default: false` + ### Generate model attributes as array (not saved) `Fabricate::attributes_for(:model_name, :number_of_generation, :array_or_callback)` generate only attributes. @@ -56,23 +105,25 @@ The hash will overwrite any fields defined in the callback function. #### Example - $results = Fabricate::attributes_for('Post', 10, function($data){ - return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; - }); - - // $results is followings : - array ( - 0 => - array ( - 'id' => 1, - 'title' => 'Lorem ipsum dolor sit amet', - 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', - 'created' => '2013-10-09 12:40:28', - 'updated' => '2013-10-09 12:40:28', - ), - 1 => - array ( - .... +```php +$results = Fabricate::attributes_for('Post', 10, function($data){ + return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; +}); + +// $results is followings : +array ( + 0 => + array ( + 'id' => 1, + 'title' => 'Lorem ipsum dolor sit amet', + 'body' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida, phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit, feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', + 'created' => '2013-10-09 12:40:28', + 'updated' => '2013-10-09 12:40:28', + ), + 1 => + array ( + .... +``` ### Generate a model instance (not saved) @@ -83,19 +134,21 @@ The hash will overwrite any fields defined in the callback function. #### Example - $result = Fabricate::build('Post', function($data){ - return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; - }); - - // $results is followings : - AppModel::__set_state(array( - 'useDbConfig' => 'default', - 'useTable' => 'posts', - 'id' => 1, - 'data' => - array ( - 'Post' => - ...... +```php +$result = Fabricate::build('Post', function($data){ + return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; +}); + +// $results is followings : +AppModel::__set_state(array( + 'useDbConfig' => 'default', + 'useTable' => 'posts', + 'id' => 1, + 'data' => + array ( + 'Post' => + ...... +``` ### Generate records to database @@ -107,9 +160,66 @@ The hash will overwrite any fields defined in the callback function. #### Example - Fabricate::create('Post', 10, function($data){ - return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; - }); +```php +Fabricate::create('Post', 10, function($data){ + return ["created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]; +}); +``` + +## Defining + +Fabricate has a name and a set of attributes when fabricating objects. +The name is used to guess the class of the object by default, + +```php +Fabricate::define('Post', ['published'=>'1']); +// or using callback block +Fabricate::define('Post', function($data, $world) { + return ['published'=>'1'] +}); +``` + +To use a different name from the class, you must specify 'class'=>:class_name into first argument as array. + +```php +Fabricate::define(['PublishedPost', 'class'=>'Post'], ['published'=>'1']); + +Fabricate::create('PublishedPost'); +``` + +You can inherit attributes from other defined set of attributes by using the 'parent' key. + +```php +Fabricate::define(['PublishedPost', 'class'=>'Post'], ['published'=>'1']); +Fabricate::define(['Author5PublishedPost', 'parent'=>'PublishedPost'], ['author_id'=>'5']); + +Fabricate::create('Author5PublishedPost'); +``` +## Associations + +It's possible to set up associations(hasOne/hasMany) within Fabricate::create(). +You can also specify a Fabricate::association(). +It will generate the attributes, and set(merge) it in the current array. + +### Usage + +```php +Fabricate::create('User', function($data, $world) { + return [ + 'user' => 'taro', + 'Post' => Fabricate::association('Post', 3), + ]; +}); +// or can overwritten by array or callback block. +Fabricate::create('User', function($data, $world) { + return [ + 'user' => 'taro', + 'Post' => Fabricate::association('Post', 3, function($data, $world) { + return ['title'=>$world->sequence('Post.title',function($i){ return "Title-${i}"; })]; + }), + ]; +}); +``` ## Sequences @@ -120,36 +230,39 @@ A sequence allows you to get a series of numbers unique within the each generati Allows you to specify the default starting number for all sequences. This can still be overridden for specific sequences. - Fabricate::config(function($config) { - $config->sequence_start = 100; - }); +```php +Fabricate::config(function($config) { + $config->sequence_start = 100; +}); +``` ### Usage - Fabricate::config(function($config) { - $config->sequence_start = 100; - }); - $results = Fabricate::attributes_for('Post', 10, function($data, $world){ - return [ - 'id'=> $world->sequence('id'), - 'title'=> $world->sequence('title', 1, function($i){ return "Title {$i}"; }) - ]; - }); - - // $results is followings : - array ( - 0 => - array ( - 'id' => 100, // starting configure sequence - 'title' => 'Title 1', // closure function returned - ... - ), - 1 => - array ( - 'id' => 101, // starting configure sequence - 'title' => 'Title 2', // closure function returned - ... - +```php +Fabricate::config(function($config) { + $config->sequence_start = 100; +}); +$results = Fabricate::attributes_for('Post', 10, function($data, $world){ + return [ + 'id'=> $world->sequence('id'), + 'title'=> $world->sequence('title', 1, function($i){ return "Title {$i}"; }) + ]; +}); + +// $results is followings : +array ( + 0 => + array ( + 'id' => 100, // starting configure sequence + 'title' => 'Title 1', // closure function returned + ... + ), + 1 => + array ( + 'id' => 101, // starting configure sequence + 'title' => 'Title 2', // closure function returned + ... +``` If you want use sequence within generation function, callback has second attribute. `$world` is FabricateContext instance. It have sequence method. @@ -158,19 +271,55 @@ If you want use sequence within generation function, callback has second attribu You should set name argument to sequence. - $world->sequence('id') +```php +$world->sequence('id') +``` If you want to specify the starting number, you can do it with a second parameter. It will always return the seed number on the first call and it will be ignored with subsequent calls. - $world->sequence('id', 10) +```php +$world->sequence('id', 10) +``` If you are generating something like an unique string, you can pass it a callback function and the callback function response will be returned. - $world->sequence('title', function($i){ return "Title {$i}"; } - // or with start number - $world->sequence('title', 1, function($i){ return "Title {$i}"; } +```php +$world->sequence('title', function($i){ return "Title {$i}"; } +// or with start number +$world->sequence('title', 1, function($i){ return "Title {$i}"; } +``` + +## Traits + +Traits allow you to group attributes together and then apply them to any fabricating objects. + +```php +Fabricate::define(['trait'=>'published'], ['published'=>'1']); +Fabricate::create('Post', function($data, $world) { + $world->traits('published'); + return ['author_id'=>5]; +}); +``` + +`traits` can specify defined names as array + +```php +Fabricate::define(['trait'=>'published'], ['published'=>'1']); +Fabricate::define(['trait'=>'author5'], function($data, $world) { return ['author_id'=>5]; }); +Fabricate::create('Post', function($data, $world) { + $world->traits(['published','author5']); + return []; +}); +``` + +## Reloading + +If you need to reset fabricate back to its original state after it has been loaded. +```php +Fabricate::clear(); +``` ## Contributing to this Plugin diff --git a/Test/Case/AllFabricateTest.php b/Test/Case/AllFabricateTest.php index 4334199..e3ec0b4 100644 --- a/Test/Case/AllFabricateTest.php +++ b/Test/Case/AllFabricateTest.php @@ -12,7 +12,7 @@ class AllFabricateTest extends PHPUnit_Framework_TestSuite { */ public static function suite() { $suite = new CakeTestSuite('All Fabricate Plugin related class tests'); - $suite->addTestDirectory(dirname(__FILE__).DS.'Lib'); + $suite->addTestDirectoryRecursive(dirname(__FILE__).DS.'Lib'); return $suite; } } diff --git a/Test/Case/Lib/Definition/FabricateDefinitionTest.php b/Test/Case/Lib/Definition/FabricateDefinitionTest.php new file mode 100644 index 0000000..e3e1506 --- /dev/null +++ b/Test/Case/Lib/Definition/FabricateDefinitionTest.php @@ -0,0 +1,26 @@ +assertEquals(['name'=>'taro'], $data); + $this->assertEquals('world', $world); + return ['name'=>'jiro']; + }); + $this->assertEquals(['name'=>'jiro'], $target->run(['name'=>'taro'], 'world')); + } + + public function testRunArrayDefinition() { + $target = new FabricateDefinition(['name'=>'jiro']); + $this->assertEquals(['name'=>'jiro'], $target->run(['name'=>'taro'], 'world')); + } + + public function testRunNullDefinition() { + $target = new FabricateDefinition(null); + $this->assertEquals([], $target->run(['name'=>'taro'], 'world')); + } +} \ No newline at end of file diff --git a/Test/Case/Lib/FabricateRegistryTest.php b/Test/Case/Lib/FabricateRegistryTest.php new file mode 100644 index 0000000..6c19a92 --- /dev/null +++ b/Test/Case/Lib/FabricateRegistryTest.php @@ -0,0 +1,48 @@ +Registry = new FabricateRegistry('FabricateRegistry'); + } + + public function testFindIfNotRegisterdAndExistsModel() { + $this->assertInstanceOf('FabricateRegistryTestPost', $this->Registry->find('FabricateRegistryTestPost')); + } + + public function testFindIfRegisteredObject() { + $this->Registry->register('FabricateRegistryTestComment', 'dummy'); + $this->assertEquals('dummy', $this->Registry->find('FabricateRegistryTestComment')); + } + + public function testFindIfRegisteredObjectOverwriteExistsModel() { + $this->Registry->register('FabricateRegistryTestPost', 'dummy'); + $this->assertEquals('dummy', $this->Registry->find('FabricateRegistryTestPost')); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFindThrowExceptionIfNotRegistered() { + $this->Registry->find('NotRegistered'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testFindThrowExceptionIfRegisteredObjectCleared() { + $this->Registry->register('FabricateRegistryTestComment', 'dummy'); + $this->Registry->clear(); + $this->Registry->find('FabricateRegistryTestComment'); + } +} \ No newline at end of file diff --git a/Test/Case/Lib/FabricateTest.php b/Test/Case/Lib/FabricateTest.php index c992722..e147a9b 100755 --- a/Test/Case/Lib/FabricateTest.php +++ b/Test/Case/Lib/FabricateTest.php @@ -2,19 +2,38 @@ App::uses('Fabricate', 'Fabricate.Lib'); +/** + * User class + * + * @package Cake.Test.Case.Model + */ +class User extends CakeTestModel { + public $hasMany = [ + 'Post' => ['foreignKey' => 'author_id'] + ]; +} + /** * Post class * * @package Cake.Test.Case.Model */ class Post extends CakeTestModel { + public $belongsTo = [ + 'Author' => ['className' => 'User', 'foreignKey' => 'author_id'], + ]; } /** * Fabricate class test case */ class FabricateTest extends CakeTestCase { - public $fixtures = ['plugin.fabricate.post']; + public $fixtures = ['plugin.fabricate.post', 'plugin.fabricate.user']; + + public function setUp() { + parent::setUp(); + Fabricate::clear(); + } public function testAttributesFor() { $results = Fabricate::attributes_for('Post', 10, function($data){ @@ -143,6 +162,105 @@ public function testCreateUsingSequence() { $this->assertEquals('Title 10', $results[9]['title']); } + /** + * @dataProvider exampleInvalidDefineParameter + * @expectedException InvalidArgumentException + */ + public function testDefineThrowExceptionIfInvalidParameter($name, $case) { + Fabricate::define($name, ['title'=>'title'], $case); + } + public function exampleInvalidDefineParameter() { + return [ + ['', 'Empty name'], + [['', 'class'=>'Post'], 'Empty name'], + [['Test', 'parent'=>'NotDefine'], 'No defined parent'], + [['Manager', 'class'=>'Person'], 'Not found class'], + ]; + } + + public function testDefineUseClassOption() { + Fabricate::define(['PublishedPost', 'class'=>'Post'], ['published'=>'1']); + $results = Fabricate::attributes_for('PublishedPost'); + $this->assertEquals('1', $results[0]['published']); + } + + public function testDefineUseNestedOption() { + Fabricate::define(['PublishedPost', 'class'=>'Post'], ['published'=>'1']); + Fabricate::define(['Author5PublishedPost', 'parent'=>'PublishedPost'], ['author_id'=>'5']); + $results = Fabricate::attributes_for('Author5PublishedPost'); + $this->assertEquals('1', $results[0]['published']); + $this->assertEquals('5', $results[0]['author_id']); + } + + public function testCreateOverwritesAnyPrimaryKeyInputWithAnEmptyIfFilterKeyIsTrue() { + Fabricate::config(function($config) { + $config->filter_key = true; + }); + Fabricate::create('Post', ["id"=>5,"created" => "2013-10-09 12:40:28", "updated" => "2013-10-09 12:40:28"]); + $model = ClassRegistry::init('Post'); + $results = $model->find('all'); + $this->assertEquals(1, $results[0]['Post']['id']); + } + public function testDefineNameOnlyOption() { + Fabricate::define('Post', ['published'=>'1']); + $results = Fabricate::attributes_for('Post'); + $this->assertEquals('1', $results[0]['published']); + } + + public function testSaveWithAssociation() { + Fabricate::define(['PublishedPost', 'class'=>'Post'], ['published'=>'1']); + Fabricate::create('User', function($data, $world) { + return [ + 'user' => 'taro', + 'Post' => Fabricate::association('PublishedPost', 3, ['id'=>false,'author_id'=>false]), + ]; + }); + + $model = ClassRegistry::init('User'); + $results = $model->find('first', ['contain'=>['Post']]); + $this->assertEquals('taro', $results['User']['user']); + $this->assertCount(3, $results['Post']); + } + + public function testDefineAndUseTrait() { + Fabricate::define(['trait'=>'published'], ['published'=>'1']); + $results = Fabricate::attributes_for('Post', function($data, $world) { + $world->traits('published'); + return ['id'=>false]; + }); + $this->assertEquals('1', $results[0]['published']); + } + + public function testDefineAndUseMultiTrait() { + Fabricate::define(['trait'=>'published'], ['published'=>'1']); + Fabricate::define(['trait'=>'author5'], function($data, $world) { return ['author_id'=>5]; }); + $results = Fabricate::attributes_for('Post', function($data, $world) { + $world->traits(['published','author5']); + return ['id'=>false]; + }); + $this->assertEquals('1', $results[0]['published']); + $this->assertEquals(5, $results[0]['author_id']); + } + + public function testDefineAndAssociationAndTraits() { + Fabricate::define(['trait'=>'published'], ['published'=>'1']); + Fabricate::define(['PublishedPost', 'class'=>'Post'], function($data, $world) { + $world->traits('published'); + return ['title'=>$world->sequence('title',function($i) { return "Title{$i}"; })]; + }); + Fabricate::create('User', function($data, $world) { + return [ + 'user' => 'taro', + 'Post' => Fabricate::association('PublishedPost', 3, ['id'=>false,'author_id'=>false]), + ]; + }); + + $model = ClassRegistry::init('User'); + $results = $model->find('first', ['contain'=>['Post']]); + $this->assertEquals('taro', $results['User']['user']); + $this->assertEquals(['1','1','1'], Hash::extract($results, 'Post.{n}.published')); + $this->assertEquals(['Title1','Title2','Title3'], Hash::extract($results, 'Post.{n}.title')); + } } diff --git a/Test/Case/Lib/Factory/FabricateFactoryTest.php b/Test/Case/Lib/Factory/FabricateFactoryTest.php new file mode 100644 index 0000000..c777ed1 --- /dev/null +++ b/Test/Case/Lib/Factory/FabricateFactoryTest.php @@ -0,0 +1,30 @@ +assertInstanceOf('FabricateModelFactory', FabricateFactory::create(ClassRegistry::init('FabricateFactoryTestPost'))); + } + + public function testCreateDefinitionFactory() { + $this->assertInstanceOf('FabricateDefinitionFactory', FabricateFactory::create(new FabricateDefinition([]))); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateThrowExceptionIfNotSupportInstance() { + FabricateFactory::create('Not Supported'); + } +} \ No newline at end of file diff --git a/Test/Fixture/UserFixture.php b/Test/Fixture/UserFixture.php new file mode 100644 index 0000000..3b06657 --- /dev/null +++ b/Test/Fixture/UserFixture.php @@ -0,0 +1,40 @@ + + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests + * @package Cake.Test.Fixture + * @since CakePHP(tm) v 1.2.0.4667 + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Class UserFixture + * + * @package Cake.Test.Fixture + */ +class UserFixture extends CakeTestFixture { + +/** + * fields property + * + * @var array + */ + public $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'user' => array('type' => 'string', 'null' => true), + 'password' => array('type' => 'string', 'null' => true), + 'created' => 'datetime', + 'updated' => 'datetime' + ); +}