diff --git a/composer.json b/composer.json index a8045d379f79..13bd3db1f974 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ }, "require-dev": { "codeigniter4/codeigniter4-standard": "^1.0", + "fzaninotto/faker": "^1.9@dev", "mikey179/vfsstream": "1.6.*", "phpunit/phpunit": "^8.5", "squizlabs/php_codesniffer": "^3.3" diff --git a/system/Language/en/Fabricator.php b/system/Language/en/Fabricator.php new file mode 100644 index 000000000000..a3efbde2a114 --- /dev/null +++ b/system/Language/en/Fabricator.php @@ -0,0 +1,20 @@ + 'Invalid model supplied for fabrication.', + 'missingFormatters' => 'No valid formatters defined.', +]; diff --git a/system/Test/Fabricator.php b/system/Test/Fabricator.php new file mode 100644 index 000000000000..05c1d1b11e49 --- /dev/null +++ b/system/Test/Fabricator.php @@ -0,0 +1,563 @@ + formatter + * @param string|null $locale Locale for Faker provider + * + * @throws \InvalidArgumentException + */ + public function __construct($model, array $formatters = null, string $locale = null) + { + if (is_string($model)) + { + // Create a new model instance + $model = model($model, false); + } + + $this->model = $model; + + // If no locale was specified then use the App default + if (is_null($locale)) + { + $locale = config('App')->defaultLocale; + } + + // There is no easy way to retrieve the locale from Faker so we will store it + $this->locale = $locale; + + // Create the locale-specific Generator + $this->faker = Factory::create($this->locale); + + // Set the formatters + $this->setFormatters($formatters); + } + + /** + * Reset state to defaults + * + * @return $this + */ + public function reset(): self + { + $this->setFormatters(); + + $this->overrides = $this->tempOverrides = []; + $this->locale = config('App')->defaultLocale; + $this->faker = Factory::create($this->locale); + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the model instance + * + * @return object Framework or compatible model + */ + public function getModel() + { + return $this->model; + } + + /** + * Returns the locale + * + * @return string + */ + public function getLocale(): string + { + return $this->locale; + } + + /** + * Returns the Faker generator + * + * @return Faker\Generator + */ + public function getFaker(): Generator + { + return $this->faker; + } + + //-------------------------------------------------------------------- + + /** + * Return and reset tempOverrides + * + * @return array + */ + public function getOverrides(): array + { + $overrides = $this->tempOverrides ?? $this->overrides; + + $this->tempOverrides = $this->overrides; + + return $overrides; + } + + /** + * Set the overrides, once or persistent + * + * @param array $overrides Array of [field => value] + * @param boolean $persist Whether these overrides should persist through the next operation + * + * @return $this + */ + public function setOverrides(array $overrides = [], $persist = true): self + { + if ($persist) + { + $this->overrides = $overrides; + } + + $this->tempOverrides = $overrides; + + return $this; + } + + //-------------------------------------------------------------------- + + /** + * Returns the current formatters + * + * @return array|null + */ + public function getFormatters(): ?array + { + return $this->formatters; + } + + /** + * Set the formatters to use. Will attempt to autodetect if none are available. + * + * @param array|null $formatters Array of [field => formatter], or null to detect + * + * @return $this + */ + public function setFormatters(array $formatters = null): self + { + if (! is_null($formatters)) + { + $this->formatters = $formatters; + } + elseif (method_exists($this->model, 'fake')) + { + $this->formatters = null; + } + else + { + $formatters = $this->detectFormatters(); + } + + return $this; + } + + /** + * Try to identify the appropriate Faker formatter for each field. + * + * @return $this + */ + protected function detectFormatters(): self + { + $this->formatters = []; + + foreach ($this->model->allowedFields as $field) + { + $this->formatters[$field] = $this->guessFormatter($field); + } + + return $this; + } + + /** + * Guess at the correct formatter to match a field name. + * + * @param $field Name of the field + * + * @return string Name of the formatter + */ + protected function guessFormatter($field): string + { + // First check for a Faker formatter of the same name - covers things like "email" + try + { + $this->faker->getFormatter($field); + return $field; + } + catch (\InvalidArgumentException $e) + { + // No match, keep going + } + + // Next look for known model fields + if (in_array($field, [$this->model->createdField, $this->model->updatedField, $this->model->deletedField])) + { + switch ($this->model->dateFormat) + { + case 'datetime': + return 'date'; + break; + + case 'date': + return 'date'; + break; + + case 'int': + return 'unixTime'; + break; + } + } + elseif ($field === $this->model->primaryKey) + { + return 'numberBetween'; + } + + // Check some common partials + foreach (['email', 'name', 'title', 'text', 'date', 'url'] as $term) + { + if (stripos($field, $term) !== false) + { + return $term; + } + } + + if (stripos($field, 'phone') !== false) + { + return 'phoneNumber'; + } + + // Nothing left, use the default + return $this->defaultFormatter; + } + + //-------------------------------------------------------------------- + + /** + * Generate new entities with faked data + * + * @param integer|null $count Optional number to create a collection + * + * @return array|object An array or object (based on returnType), or an array of returnTypes + */ + public function make(int $count = null) + { + // If a singleton was requested then go straight to it + if (is_null($count)) + { + return $this->model->returnType === 'array' + ? $this->makeArray() + : $this->makeObject(); + } + + $return = []; + + for ($i = 0; $i < $count; $i++) + { + $return[] = $this->model->returnType === 'array' + ? $this->makeArray() + : $this->makeObject(); + } + + return $return; + } + + /** + * Generate an array of faked data + * + * @return array An array of faked data + * + * @throws \RuntimeException + */ + public function makeArray() + { + if (! is_null($this->formatters)) + { + $result = []; + + foreach ($this->formatters as $field => $formatter) + { + $result[$field] = $this->faker->{$formatter}; + } + } + + // If no formatters were defined then look for a model fake() method + elseif (method_exists($this->model, 'fake')) + { + $result = $this->model->fake($this->faker); + + // This should cover entities + if (method_exists($result, 'toArray')) + { + $result = $result->toArray(); + } + // Try to cast it + else + { + $result = (array) $result; + } + } + + // Nothing left to do but give up + else + { + throw new \RuntimeException(lang('Fabricator.missingFormatters')); + } + + // Replace overridden fields + return array_merge($result, $this->getOverrides()); + } + + /** + * Generate an object of faked data + * + * @param string|null $className Class name of the object to create; null to use model default + * + * @return object An instance of the class with faked data + * + * @throws \RuntimeException + */ + public function makeObject(string $className = null): object + { + if (is_null($className)) + { + if ($this->model->returnType === 'object' || $this->model->returnType === 'array') + { + $className = 'stdClass'; + } + else + { + $className = $this->model->returnType; + } + } + + // If using the model's fake() method then check it for the correct return type + if (is_null($this->formatters) && method_exists($this->model, 'fake')) + { + $result = $this->model->fake($this->faker); + + if ($result instanceof $className) + { + // Set overrides manually + foreach ($this->getOverrides() as $key => $value) + { + $result->{$key} = $value; + } + + return $result; + } + } + + // Get the array values and apply them to the object + $array = $this->makeArray(); + $object = new $className(); + + // Check for the entity method + if (method_exists($object, 'fill')) + { + $object->fill($array); + } + else + { + foreach ($array as $key => $value) + { + $object->{$key} = $value; + } + } + + return $object; + } + + //-------------------------------------------------------------------- + + /** + * Generate new entities from the database + * + * @param integer|null $count Optional number to create a collection + * @param array $override Array of data to add/override + * @param boolean $mock Whether to execute or mock the insertion + * + * @return array|object An array or object (based on returnType), or an array of returnTypes + */ + public function create(int $count = null, bool $mock = false) + { + // Intercept mock requests + if ($mock) + { + return $this->createMock($count); + } + + $ids = []; + + // Iterate over new entities and insert each one, storing insert IDs + foreach ($this->make($count ?? 1) as $result) + { + $ids[] = $this->model->insert($result, true); + } + + return $this->model->withDeleted()->find(is_null($count) ? reset($ids) : $ids); + } + + /** + * Generate new database entities without actually inserting them + * + * @param integer|null $count Optional number to create a collection + * + * @return array|object An array or object (based on returnType), or an array of returnTypes + */ + protected function createMock(int $count = null) + { + switch ($this->model->dateFormat) + { + case 'datetime': + $datetime = date('Y-m-d H:i:s'); + case 'date': + $datetime = date('Y-m-d'); + default: + $datetime = time(); + } + + // Determine which fields we will need + $fields = []; + + if ($this->model->useTimestamps) + { + $fields[$this->model->createdField] = $datetime; + $fields[$this->model->updatedField] = $datetime; + } + + if ($this->model->useSoftDeletes) + { + $fields[$this->model->deletedField] = null; + } + + // Iterate over new entities and add the necessary fields + $return = []; + foreach ($this->make($count ?? 1) as $i => $result) + { + // Set the ID + $fields[$this->model->primaryKey] = $i; + + // Merge fields + if (is_array($result)) + { + $result = array_merge($result, $fields); + } + else + { + foreach ($fields as $key => $value) + { + $result->{$key} = $value; + } + } + + $return[] = $result; + } + + return is_null($count) ? reset($return) : $return; + } +} diff --git a/tests/_support/Models/FabricatorModel.php b/tests/_support/Models/FabricatorModel.php new file mode 100644 index 000000000000..de99daece4b2 --- /dev/null +++ b/tests/_support/Models/FabricatorModel.php @@ -0,0 +1,31 @@ + $faker->ipv4, + 'description' => $faker->words(10), + ]; + } +} diff --git a/tests/system/Database/Live/FabricatorLiveTest.php b/tests/system/Database/Live/FabricatorLiveTest.php new file mode 100644 index 000000000000..7f68bd439a98 --- /dev/null +++ b/tests/system/Database/Live/FabricatorLiveTest.php @@ -0,0 +1,37 @@ +create(); + + $this->seeInDatabase('user', ['name' => $result->name]); + } + + public function testCreateAddsCountToDatabase() + { + $count = 10; + + $fabricator = new Fabricator(UserModel::class); + + // Some countries violate the 40 character limit so override that + $fabricator->setOverrides(['country' => 'France']); + + $result = $fabricator->create($count); + + $this->seeNumRecords($count, 'user', []); + } + +} diff --git a/tests/system/Test/FabricatorTest.php b/tests/system/Test/FabricatorTest.php new file mode 100644 index 000000000000..9ad3517414be --- /dev/null +++ b/tests/system/Test/FabricatorTest.php @@ -0,0 +1,401 @@ + 'name', + 'email' => 'email', + 'country' => 'country', + 'deleted_at' => 'date', + ]; + + //-------------------------------------------------------------------- + + public function testConstructorWithString() + { + $fabricator = new Fabricator(UserModel::class); + + $this->assertInstanceOf(Fabricator::class, $fabricator); + } + + public function testConstructorWithInstance() + { + $model = new UserModel(); + + $fabricator = new Fabricator($model); + + $this->assertInstanceOf(Fabricator::class, $fabricator); + } + + public function testConstructorSetsFormatters() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + + $this->assertEquals($this->formatters, $fabricator->getFormatters()); + } + + public function testConstructorGuessesFormatters() + { + $fabricator = new Fabricator(UserModel::class, null); + + $this->assertEquals($this->formatters, $fabricator->getFormatters()); + } + + public function testConstructorDefaultsToAppLocale() + { + $fabricator = new Fabricator(UserModel::class); + + $this->assertEquals(config('App')->defaultLocale, $fabricator->getLocale()); + } + + public function testConstructorUsesProvidedLocale() + { + $locale = 'fr_FR'; + + $fabricator = new Fabricator(UserModel::class, null, $locale); + + $this->assertEquals($locale, $fabricator->getLocale()); + } + + //-------------------------------------------------------------------- + + public function testModelUsesNewInstance() + { + // Inject the wrong model for UserModel to show it is ignored by Fabricator + $mock = new FabricatorModel(); + ModelFactory::injectMock('Tests\Support\Models\UserModel', $mock); + + $fabricator = new Fabricator(UserModel::class); + $this->assertInstanceOf(UserModel::class, $fabricator->getModel()); + } + + public function testGetModelReturnsModel() + { + $fabricator = new Fabricator(UserModel::class); + $this->assertInstanceOf(UserModel::class, $fabricator->getModel()); + + $model = new UserModel(); + $fabricator2 = new Fabricator($model); + $this->assertInstanceOf(UserModel::class, $fabricator2->getModel()); + } + + public function testGetFakerReturnsUsableGenerator() + { + $fabricator = new Fabricator(UserModel::class); + + $faker = $fabricator->getFaker(); + + $this->assertIsNumeric($faker->randomDigit); + } + + //-------------------------------------------------------------------- + + public function testSetFormattersChangesFormatters() + { + $formatters = ['boo' => 'hiss']; + $fabricator = new Fabricator(UserModel::class); + + $fabricator->setFormatters($formatters); + + $this->assertEquals($formatters, $fabricator->getFormatters()); + } + + public function testSetFormattersDetectsFormatters() + { + $formatters = ['boo' => 'hiss']; + $fabricator = new Fabricator(UserModel::class, $formatters); + + $fabricator->setFormatters(); + + $this->assertEquals($this->formatters, $fabricator->getFormatters()); + } + + public function testDetectFormattersDetectsFormatters() + { + $formatters = ['boo' => 'hiss']; + $fabricator = new Fabricator(UserModel::class, $formatters); + + $method = $this->getPrivateMethodInvoker($fabricator, 'detectFormatters'); + + $method(); + + $this->assertEquals($this->formatters, $fabricator->getFormatters()); + } + + //-------------------------------------------------------------------- + + public function testSetOverridesSets() + { + $overrides = ['name' => 'Steve']; + $fabricator = new Fabricator(UserModel::class); + + $fabricator->setOverrides($overrides); + + $this->assertEquals($overrides, $fabricator->getOverrides()); + } + + public function testSetOverridesDefaultPersists() + { + $overrides = ['name' => 'Steve']; + $fabricator = new Fabricator(UserModel::class); + + $fabricator->setOverrides($overrides); + $fabricator->getOverrides(); + + $this->assertEquals($overrides, $fabricator->getOverrides()); + } + + public function testSetOverridesOnce() + { + $overrides = ['name' => 'Steve']; + $fabricator = new Fabricator(UserModel::class); + + $fabricator->setOverrides($overrides, false); + $fabricator->getOverrides(); + + $this->assertEquals([], $fabricator->getOverrides()); + } + + //-------------------------------------------------------------------- + + public function testGuessFormattersReturnsActual() + { + $fabricator = new Fabricator(UserModel::class); + + $method = $this->getPrivateMethodInvoker($fabricator, 'guessFormatter'); + + $field = 'catchPhrase'; + $formatter = $method($field); + + $this->assertEquals($field, $formatter); + } + + public function testGuessFormattersFieldReturnsDateFormat() + { + $fabricator = new Fabricator(UserModel::class); + + $method = $this->getPrivateMethodInvoker($fabricator, 'guessFormatter'); + + $field = 'created_at'; + $formatter = $method($field); + + $this->assertEquals('date', $formatter); + } + + public function testGuessFormattersPrimaryReturnsNumberBetween() + { + $fabricator = new Fabricator(UserModel::class); + + $method = $this->getPrivateMethodInvoker($fabricator, 'guessFormatter'); + + $field = 'id'; + $formatter = $method($field); + + $this->assertEquals('numberBetween', $formatter); + } + + public function testGuessFormattersMatchesPartial() + { + $fabricator = new Fabricator(UserModel::class); + + $method = $this->getPrivateMethodInvoker($fabricator, 'guessFormatter'); + + $field = 'business_email'; + $formatter = $method($field); + + $this->assertEquals('email', $formatter); + } + + public function testGuessFormattersFallback() + { + $fabricator = new Fabricator(UserModel::class); + + $method = $this->getPrivateMethodInvoker($fabricator, 'guessFormatter'); + + $field = 'zaboomafoo'; + $formatter = $method($field); + + $this->assertEquals($fabricator->defaultFormatter, $formatter); + } + + //-------------------------------------------------------------------- + + public function testMakeArrayReturnsArray() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + + $result = $fabricator->makeArray(); + + $this->assertIsArray($result); + } + + public function testMakeArrayUsesOverrides() + { + $overrides = ['name' => 'The Admiral']; + + $fabricator = new Fabricator(UserModel::class, $this->formatters); + $fabricator->setOverrides($overrides); + + $result = $fabricator->makeArray(); + + $this->assertEquals($overrides['name'], $result['name']); + } + + public function testMakeArrayReturnsValidData() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + + $result = $fabricator->makeArray(); + + $this->assertEquals($result['email'], filter_var($result['email'], FILTER_VALIDATE_EMAIL)); + } + + public function testMakeArrayUsesFakeMethod() + { + $fabricator = new Fabricator(FabricatorModel::class); + + $result = $fabricator->makeArray(); + + $this->assertEquals($result['name'], filter_var($result['name'], FILTER_VALIDATE_IP)); + } + + //-------------------------------------------------------------------- + + public function testMakeObjectReturnsModelReturnType() + { + $fabricator = new Fabricator(EntityModel::class); + $expected = $fabricator->getModel()->returnType; + + $result = $fabricator->makeObject(); + + $this->assertInstanceOf($expected, $result); + } + + public function testMakeObjectReturnsProvidedClass() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + $className = 'Tests\Support\Models\SimpleEntity'; + + $result = $fabricator->makeObject($className); + + $this->assertInstanceOf($className, $result); + } + + public function testMakeObjectReturnsStdClassForArrayReturnType() + { + $fabricator = new Fabricator(EventModel::class); + + $result = $fabricator->makeObject(); + + $this->assertInstanceOf(\stdClass::class, $result); + } + + public function testMakeObjectReturnsStdClassForObjectReturnType() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + + $result = $fabricator->makeObject(); + + $this->assertInstanceOf(\stdClass::class, $result); + } + + public function testMakeObjectUsesOverrides() + { + $overrides = ['name' => 'The Admiral']; + + $fabricator = new Fabricator(UserModel::class, $this->formatters); + $fabricator->setOverrides($overrides); + + $result = $fabricator->makeObject(); + + $this->assertEquals($overrides['name'], $result->name); + } + + public function testMakeObjectReturnsValidData() + { + $fabricator = new Fabricator(UserModel::class, $this->formatters); + + $result = $fabricator->makeObject(); + + $this->assertEquals($result->email, filter_var($result->email, FILTER_VALIDATE_EMAIL)); + } + + public function testMakeObjectUsesFakeMethod() + { + $fabricator = new Fabricator(FabricatorModel::class); + + $result = $fabricator->makeObject(); + + $this->assertEquals($result->name, filter_var($result->name, FILTER_VALIDATE_IP)); + } + + //-------------------------------------------------------------------- + + public function testMakeReturnsSingleton() + { + $fabricator = new Fabricator(UserModel::class); + + $result = $fabricator->make(); + + $this->assertInstanceOf('stdClass', $result); + } + + public function testMakeReturnsExpectedCount() + { + $fabricator = new Fabricator(UserModel::class); + + $count = 10; + $result = $fabricator->make($count); + + $this->assertIsArray($result); + $this->assertCount($count, $result); + } + + //-------------------------------------------------------------------- + + public function testCreateMockReturnsSingleton() + { + $fabricator = new Fabricator(UserModel::class); + + $result = $fabricator->create(null, true); + + $this->assertInstanceOf('stdClass', $result); + } + + public function testCreateMockReturnsExpectedCount() + { + $fabricator = new Fabricator(UserModel::class); + + $count = 10; + $result = $fabricator->create($count, true); + + $this->assertIsArray($result); + $this->assertCount($count, $result); + } + + public function testCreateMockSetsDatabaseFields() + { + $fabricator = new Fabricator(FabricatorModel::class); + + $result = $fabricator->create(null, true); + + $this->assertIsInt($result->id); + $this->assertIsInt($result->created_at); + $this->assertIsInt($result->updated_at); + + $this->assertObjectHasAttribute('deleted_at', $result); + $this->assertNull($result->deleted_at); + } +} diff --git a/user_guide_src/source/testing/fabricator.rst b/user_guide_src/source/testing/fabricator.rst new file mode 100644 index 000000000000..a08fc2a8d071 --- /dev/null +++ b/user_guide_src/source/testing/fabricator.rst @@ -0,0 +1,200 @@ +#################### +Generating Test Data +#################### + +Often you will need sample data for your application to run its tests. The ``Fabricator`` class +uses fzaninotto's `Faker `_ to turn models into generators +of random data. Use fabricators in your seeds or test cases to stage fake data for your unit tests. + +Loading Fabricators +=================== + +At its most basic a fabricator takes the model to act on:: + + use App\Models\UserModel; + use CodeIgniter\Test\Fabricator; + + $fabricator = new Fabricator(UserModel::class); + +The parameter can be a string specifying the name of the model, or an instance of the model itself:: + + $model = new UserModel($testDbConnection); + + $fabricator = new Fabricator($model); + +Defining Formatters +=================== + +Faker generates data by requesting it from a formatter. With no formatters defined, ``Fabricator`` will +attempt to guess at the most appropriate fit based on the field name and properties of the model it +represents, falling back on ``$fabricator->defaultFormatter``. This may be fine if your field names +correspond with common formatters, or if you don't care much about the content of the fields, but most +of the time you will want to specify the formatters to use as the second parameter to the constructor:: + + $formatters = [ + 'first' => 'firstName', + 'email' => 'email', + 'phone' => 'phoneNumber', + 'avatar' => 'imageUrl', + ]; + + $fabricator = new Fabricator(UserModel::class, $formatters); + +You can also change the formatters after a fabricator is initialized by using the ``setFormatters()`` method. + +**Advanced Formatting** + +Sometimes the default return of a formatter is not enough. Faker providers allow parameters to most formatters +to further limit the scope of random data. A fabricator will check its representative model for the ``fake()`` +method where you can define exactly what the faked data should look like:: + + class UserModel + { + public function fake(Generator &$faker) + { + return [ + 'first' => $faker->firstName, + 'email' => $faker->email, + 'phone' => $faker->phoneNumber, + 'avatar' => Faker\Provider\Image::imageUrl(800, 400), + 'login' => config('Auth')->allowRemembering ? date('Y-m-d') : null, + ]; + } + +Notice in this example how the first three values are equivalent to the formatters from before. However for ``avatar`` +we have requested an image size other than the default and ``login`` uses a conditional based on app configuration, +neither of which are possible using the ``$formatters`` parameter. +You may want to keep your test data separate from your production models, so it is a good practice to define +a child class in your test support folder:: + + namespace Tests\Support\Models; + + class UserFabricator extends \App\Models\UserModel + { + public function fake(&$faker) + { + +Localization +============ + +Faker supports a lot of different locales. Check their documentation to determine which providers +support your locale. Specify a locale in the third parameter while initiating a fabricator:: + + $fabricator = new Fabricator(UserModel::class, null, 'fr_FR'); + +If no locale is specified it will use the one defined in **app/Config/App.php** as ``defaultLocale``. +You can check the locale of an existing fabricator using its ``getLocale()`` method. + +Faking the Data +=============== + +Once you have a properly-initialized fabricator it is easy to generate test data with the ``make()`` command:: + + $fabricator = new Fabricator(UserFabricator::class); + $testUser = $fabricator->make(); + print_r($testUser); + +You might get back something like this:: + + array( + 'first' => "Maynard", + 'email' => "king.alford@example.org", + 'phone' => "201-886-0269 x3767", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + ) + +You can also get a lot of them back by supplying a count:: + + $users = $fabricator->make(10); + +The return type of ``make()`` mimics what is defined in the representative model, but you can +force a type using the methods directly:: + + $userArray = $fabricator->makeArray(); + $userObject = $fabricator->makeObject(); + $userEntity = $fabricator->makeObject('App\Entities\User'); + +The return from ``make()`` is ready to be used in tests or inserted into the database. Alternatively +``Fabricator`` includes the ``create()`` command to insert it for you, and return the result. Due +to model callbacks, database formatting, and special keys like primary and timestamps the return +from ``create()`` can differ from ``make()``. You might get back something like this:: + + array( + 'id' => 1, + 'first' => "Rachel", + 'email' => "bradley72@gmail.com", + 'phone' => "741-241-2356", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + 'created_at' => "2020-05-08 14:52:10", + 'updated_at' => "2020-05-08 14:52:10", + ) + +Similar to ``make()`` you can supply a count to insert and return an array of objects:: + + $users = $fabricator->create(100); + +Finally, there may be times you want to test with the full database object but you are not actually +using a database. ``create()`` takes a second parameter to allowing mocking the object, returning +the object with extra database fields above without actually touching the database:: + + $user = $fabricator(null, true); + + $this->assertIsNumeric($user->id); + $this->dontSeeInDatabase('user', ['id' => $user->id]); + +Specifying Test Data +==================== + +Generated data is great, but sometimes you may want to supply a specific field for a test without +compromising your formatters configuration. Rather then creating a new fabricator for each variant +you can use ``setOverrides()`` to specify the value for any fields:: + + $fabricator->setOverrides(['first' => 'Bobby']); + $bobbyUser = $fabricator->make(); + +Now any data generated with ``make()`` or ``create()`` will always use "Bobby" for the ``first`` field: + + array( + 'first' => "Bobby", + 'email' => "latta.kindel@company.org", + 'phone' => "251-806-2169", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + ) + + array( + 'first' => "Bobby", + 'email' => "melissa.strike@fabricon.us", + 'phone' => "525-214-2656 x23546", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + ) + +``setOverrides()`` can take a second parameter to indicate whether this should be a persistent +override or only for a single action:: + + $fabricator->setOverrides(['first' => 'Bobby'], $persist = false); + $bobbyUser = $fabricator->make(); + $bobbyUser = $fabricator->make(); + +Notice after the first return the fabricator stops using the overrides:: + + array( + 'first' => "Bobby", + 'email' => "belingadon142@example.org", + 'phone' => "741-857-1933 x1351", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + ) + + array( + 'first' => "Hans", + 'email' => "hoppifur@metraxalon.com", + 'phone' => "487-235-7006", + 'avatar' => "http://lorempixel.com/800/400/", + 'login' => null, + ) + +If no second parameter is supplied then passed values will persist by default. diff --git a/user_guide_src/source/testing/index.rst b/user_guide_src/source/testing/index.rst index d582bbaa9920..74d6239a292d 100644 --- a/user_guide_src/source/testing/index.rst +++ b/user_guide_src/source/testing/index.rst @@ -10,6 +10,7 @@ The following sections should get you quickly testing your applications. Getting Started Database + Generating Data Controller Testing HTTP Testing benchmark