1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<?php
namespace Codeception\Module;
use Codeception\Lib\Interfaces\DataMapper;
use Codeception\Lib\Interfaces\DependsOnModule;
use Codeception\Lib\Interfaces\ORM;
use Codeception\Exception\ModuleException;
use Codeception\Lib\Interfaces\RequiresPackage;
use Codeception\TestInterface;
use League\FactoryMuffin\FactoryMuffin;
use League\FactoryMuffin\Stores\RepositoryStore;
/**
* DataFactory allows you to easily generate and create test data using [**FactoryMuffin**](https://github.com/thephpleague/factory-muffin).
* DataFactory uses an ORM of your application to define, save and cleanup data. Thus, should be used with ORM or Framework modules.
*
* This module requires packages installed:
*
* ```json
* {
* "league/factory-muffin": "^3.0",
* }
* ```
*
* Generation rules can be defined in a factories file. You will need to create `factories.php` (it is recommended to store it in `_support` dir)
* Follow [FactoryMuffin documentation](https://github.com/thephpleague/factory-muffin) to set valid rules.
* Random data provided by [Faker](https://github.com/fzaninotto/Faker) library.
*
* ```php
* <?php
* use League\FactoryMuffin\Faker\Facade as Faker;
*
* $fm->define(User::class)->setDefinitions([
* 'name' => Faker::name(),
*
* // generate email
* 'email' => Faker::email(),
* 'body' => Faker::text(),
*
* // generate a profile and return its Id
* 'profile_id' => 'factory|Profile'
* ]);
* ```
*
* Configure this module to load factory definitions from a directory.
* You should also specify a module with an ORM as a dependency.
*
* ```yaml
* modules:
* enabled:
* - Yii2:
* configFile: path/to/config.php
* - DataFactory:
* factories: tests/_support/factories
* depends: Yii2
* ```
*
* (you can also use Laravel5 and Phalcon).
*
* In this example factories are loaded from `tests/_support/factories` directory. Please note that this directory is relative from the codeception.yml file (so for Yii2 it would be codeception/_support/factories).
* You should create this directory manually and create PHP files in it with factories definitions following [official documentation](https://github.com/thephpleague/factory-muffin#usage).
*
* In cases you want to use data from database inside your factory definitions you can define them in Helper.
* For instance, if you use Doctrine, this allows you to access `EntityManager` inside a definition.
*
* To proceed you should create Factories helper via `generate:helper` command and enable it:
*
* ```
* modules:
* enabled:
* - DataFactory:
* depends: Doctrine2
* - \Helper\Factories
*
* ```
*
* In this case you can define factories from a Helper class with `_define` method.
*
* ```php
* <?php
* public function _beforeSuite()
* {
* $factory = $this->getModule('DataFactory');
* // let us get EntityManager from Doctrine
* $em = $this->getModule('Doctrine2')->_getEntityManager();
*
* $factory->_define(User::class, [
*
* // generate random user name
* // use League\FactoryMuffin\Faker\Facade as Faker;
* 'name' => Faker::name(),
*
* // get real company from database
* 'company' => $em->getRepository(Company::class)->find(),
*
* // let's generate a profile for each created user
* // receive an entity and set it via `setProfile` method
* // UserProfile factory should be defined as well
* 'profile' => 'entity|'.UserProfile::class
* ]);
* }
* ```
*
* Factory Definitions are described in official [Factory Muffin Documentation](https://github.com/thephpleague/factory-muffin)
*
* ### Related Models Generators
*
* If your module relies on other model you can generate them both.
* To create a related module you can use either `factory` or `entity` prefix, depending on ORM you use.
*
* In case your ORM expects an Id of a related record (Eloquent) to be set use `factory` prefix:
*
* ```php
* 'user_id' => 'factory|User'
* ```
*
* In case your ORM expects a related record itself (Doctrine) then you should use `entity` prefix:
*
* ```php
* 'user' => 'entity|User'
* ```
*/
class DataFactory extends \Codeception\Module implements DependsOnModule, RequiresPackage
{
protected $dependencyMessage = <<<EOF
ORM module (like Doctrine2) or Framework module with ActiveRecord support is required:
--
modules:
enabled:
- DataFactory:
depends: Doctrine2
--
EOF;
/**
* ORM module on which we we depend on.
*
* @var ORM
*/
public $ormModule;
/**
* @var FactoryMuffin
*/
public $factoryMuffin;
protected $config = ['factories' => null];
public function _requires()
{
return [
'League\FactoryMuffin\FactoryMuffin' => '"league/factory-muffin": "^3.0"',
];
}
public function _beforeSuite($settings = [])
{
$store = $this->getStore();
$this->factoryMuffin = new FactoryMuffin($store);
if ($this->config['factories']) {
foreach ((array) $this->config['factories'] as $factoryPath) {
$realpath = realpath(codecept_root_dir().$factoryPath);
if ($realpath === false) {
throw new ModuleException($this, 'The path to one of your factories is not correct. Please specify the directory relative to the codeception.yml file (ie. _support/factories).');
}
$this->factoryMuffin->loadFactories($realpath);
}
}
}
/**
* @return StoreInterface|null
*/
protected function getStore()
{
return $this->ormModule instanceof DataMapper
? new RepositoryStore($this->ormModule->_getEntityManager()) // for Doctrine
: null;
}
public function _inject(ORM $orm)
{
$this->ormModule = $orm;
}
public function _after(TestInterface $test)
{
$skipCleanup = array_key_exists('cleanup', $this->config) && $this->config['cleanup'] === false;
if ($skipCleanup || $this->ormModule->_getConfig('cleanup')) {
return;
}
$this->factoryMuffin->deleteSaved();
}
public function _depends()
{
return ['Codeception\Lib\Interfaces\ORM' => $this->dependencyMessage];
}
/**
* Creates a model definition. This can be used from a helper:.
*
* ```php
* $this->getModule('{{MODULE_NAME}}')->_define('User', [
* 'name' => $faker->name,
* 'email' => $faker->email
* ]);
*
* ```
*
* @param string $model
* @param array $fields
*
* @return \League\FactoryMuffin\Definition
*
* @throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
*/
public function _define($model, $fields)
{
return $this->factoryMuffin->define($model)->setDefinitions($fields);
}
/**
* Generates and saves a record,.
*
* ```php
* $I->have('User'); // creates user
* $I->have('User', ['is_active' => true]); // creates active user
* ```
*
* Returns an instance of created user.
*
* @param string $name
* @param array $extraAttrs
*
* @return object
*/
public function have($name, array $extraAttrs = [])
{
return $this->factoryMuffin->create($name, $extraAttrs);
}
/**
* Generates a record instance.
*
* This does not save it in the database. Use `have` for that.
*
* ```php
* $user = $I->make('User'); // return User instance
* $activeUser = $I->make('User', ['is_active' => true]); // return active user instance
* ```
*
* Returns an instance of created user without creating a record in database.
*
* @param string $name
* @param array $extraAttrs
*
* @return object
*/
public function make($name, array $extraAttrs = [])
{
return $this->factoryMuffin->instance($name, $extraAttrs);
}
/**
* Generates and saves a record multiple times.
*
* ```php
* $I->haveMultiple('User', 10); // create 10 users
* $I->haveMultiple('User', 10, ['is_active' => true]); // create 10 active users
* ```
*
* @param string $name
* @param int $times
* @param array $extraAttrs
*
* @return \object[]
*/
public function haveMultiple($name, $times, array $extraAttrs = [])
{
return $this->factoryMuffin->seed($times, $name, $extraAttrs);
}
}