In a Laravel project, your tests probably access the $this->faker
instance a lot to generate random data. But did you know you can customize the faker instance?
Add custom faker methods
In your tests you might have to generate some project-specific fake data. It is possible to hide that code in a custom faker method. To get this working, you need to create a class that extends the Faker\Provider\Base
class. In that class, you can start defining your own faker methods.
namespace Tests\Faker;
use Faker\Provider\Base;
class CustomProvider extends Base
{
public function fooBar()
{
// Generate and return some custom fake data ...
}
}
You even have the option to override existing faker methods. For example, when requesting a random timezone from faker, you might notice that faker sometimes returns a timezone that is not known in a mysql database. If that causes issues in your tests, you can fix that by overriding the method and defining a list of valid mysql timezones and pick a random timezone from that list.
Register custom faker provider
Now that you have created some custom faker methods in your custom faker provider class, you need to register the provider so that faker uses it. To do that, you must customize the Faker\Generator
singleton and add a provider to the singleton. You should add this code in the register()
method of service provider.
$this->app->singleton(\Faker\Generator::class, function () {
$faker = \Faker\Factory::create();
$faker->addProvider(new \Tests\FakerCustomProvider($faker)))
return $faker;
});
If you use Laravel’s global fake()
method in your project, then you will also have to add the following code:
$this->app->bind(\Faker\Generator::class . ':' . config('app.faker_locale'), \Faker\Generator::class);
Now you can start using your custom faker methods!
$this->faker->fooBar();
// OR
fake()->fooBar();
Add chainable faker methods
You can even take it a step further and add custom faker methods that can be used on top of existing faker methods, similar to faker’s optional()
and unique()
methods.
In the projects at work, we use spatie/laravel-translable
package to save translations in json columns. That means in a lot of tests and model factories, we have to manually create an array with locales as keys.
$data = [
"en" => $this->faker->sentence(),
"nl" => $this->faker->sentence(),
"fr" => $this->faker->sentence(),
];
Would it not be nice if we don’t have to repeat this setup everywhere? Well, that is possible.
First, let’s define a translatable method on our custom provider class. That method will return a new class called TranslatableGenerator
.
// in Tests\Faker\CustomProvider
public function translatable() : TranslatableGenerator
{
return new TranslatableGenerator($this->generator);
}
In that class we receive the original faker generator object. If we now intercept any methods call using the magic __call
, we can create an array before calling the chained faker method and then return the created array.
class TranslatableGenerator
{
public function __construct(
protected Generator $generator
) {
}
public function __call($name, $arguments) : mixed
{
return collect(config('app.supported_locales'))->mapWithKeys(function (string $locale) use ($name, $arguments) {
return [$locale => call_user_func_array([$this->generator, $name], $arguments)];
})->all();
}
}
If you check the source code of faker's built-in unique()
and optional()
methods, you will see that those work in a similar way.
If you now write the following code, an array containing random sentences with locales as keys will be returned:
$this->faker->translatable()->sentence();
Adding custom (chainable) methods to the faker instance of your project will make your tests a bit compacter and more readable, in my opinion.