Learn to Use Model Factories in Laravel

14 3,768

Laravel has a feature called model factories that allows you to build fake data for your models. It is very useful for testing and seeding fake data into your database to see your code in action before any real user data comes in. Let’s go through this feature and see how we can use it by building a Laravel application.

Starting

Let’s start by creating a new project named Factories. To do this, run the following command:

laravel new Factories

This will create a new Laravel project within a folder named Factories.

Creating a new Model

Now, let’s create a Post model and a migration for it. To do this run the following command from the project root directory:

php artisan make:model Post -m

This command will create a new Post model and because we added -m or --migration flag, it will create a migration for it as well. This command will create two files at app/Post.php and database/migrations/create_posts_table.php. Create posts table relationship with the user by opening app/User.php and defining the following relation:

public function posts()
{
    return $this->hasMany(Post::class);
}

Setting up Database

Now, go to your .env file and change credentials for your database. Since it is just a dummy project, I will go with SQLite. To use SQLite, set DB_CONNECTION to sqlite and remove all other DB_* lines from your .env. Your .env database part now look like this:

DB_CONNECTION=sqlite

Now that you have set DB_CONNECTION to sqlite, create a file named database.sqlite in your database directory in the root of your application. Also, run the following command to configure your cache.

php artisan config:cache

Now let’s set up our migration.

Setting up Migration

Open your posts table migration file in database/migrations/create_posts_table.php and change the up method to the following:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id');
        $table->string('title');
        $table->text('description');
        $table->timestamps();
    });
}

In the create_posts_table.php migration, we are creating a user_id that will be a relationship to the users table, title for the post which is a string, and description which will contain the contents for the post.

After you have set-up the migration, run the database migration from the artisan console:

php artisan migrate

Now that we have run the migration, let’s create some database seeds to create fake data for our project.

Creating Database Seeds

Database seeds are a way of creating fake data and adding it to your database. It helps you get fake data quickly than creating it manually. Let’s create a seed for users and posts table. To do this run both of the following commands one by one:

php artisan make:seeder UsersTableSeeder
php artisan make:seeder PostsTableSeeder

Now that we have created seeds for users and posts table, they need some instructions. We have to create model factories for that.

Creating Model Factories

From Laravel 5.5, we create different files for the factories of each model. It is better than defining all factories inside a single file. If you are using earlier versions of Laravel, you have to define factories in database/factories/ModelFactory.php file. Since, we are using Laravel 5.5, open database/factories/UserFactory.php and you will see a factory for users table defined already.

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token' => str_random(10),
    ];
});

In the above code, we are defining a factory for App\User::class model and passing it as the first parameter. Then we have a callback function that defines the data and returns it in an array. Array keys are the name of the columns of the users table. We are using Faker which is a very useful library for creating fake data. Here’s a description of all the faker properties used.

  • $faker->name: Creates a fake name like John Doe
  • $faker->unique()->safeEmail: Create a unique email that uses example.org that is reserved for testing purposes. So, even if it generates a real email, it is not an issue.
  • For password, we are defining an encrypted version of the word secret. It will create the same password for all the users which will be secret (encrypted). If you want the password for the users to be random, you could instead use something like this:
    'password' => password_hash(str_random(10)),

    It will create a random string of 10 characters and then encrypt it safely using PHP function password_hash.

Now let’s create a new factory for our posts table. To do this: run the following command:

php artisan make:factory PostFactory

It will create a new factory at database/factories/PostFactory.php. Here are the completed contents of the PostFactory.php file

<?php

use Faker\Generator as Faker;

$factory->define(App\Post::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence(5),
        'description' => $faker->text(),
        'user_id' => factory('App\User')->create()->id,
    ];
});

Here’s a description of all of the faker properties used:

  • $faker->sentence(5): Creates a dummy sentence with five words
  • $faker->test(): Creates some dummy text
  • factory(‘App\User’)->create()->id: Creates a new user via User factory and get its id

Now that we have created our factories, let’s complete our seed classes. Open the UsersTableSeeder and modify its run method to the following:

public function run()
{
    factory('App\User', 10)->create();
}

factory('App\User', 10) will build a User class through its factory and it will create it by saving it into the database. Since we want 10 users in our database, it will do it 10 times.

Now open PostsTableSeeder and modify its run method to the following as well:

public function run()
{
    factory('App\Post', 10)->create();
}

It will create 10 posts in the database as well as 10 users because in Post factory we create a new user through its User Factory and get its id.

Now that we have created our table seeds, go to database/seeds/DatabaseSeeder.php file and modify its up method to the following:

public function run()
{
    // Disable all mass assignment restrictions
    Model::unguard();

    $this->call(PostsTableSeeder::class);

    // Re enable all mass assignment restrictions
    Model::reguard();
}

Now that we have added PostsTableSeeder in our DatabaseSeeder.php , we can run it through the console. Since, we have already migrated our database, run the following command to seed your database:

php artisan db:seed

Above command will run the run method on DatabaseSeeder class. Now our database will contain some fake data generated from model factories. PostsTableSeeder will create 10 users and 10 posts with some dummy title and description.

You can also seed any Seeder class by passing the --class option. If you want to seed UsersTableSeeder you can run the following command. It’s just for information, we are not doing it in our project.

php artisan db:seed --class=UsersTableSeeder

Testing with Factories

Let’s see how we can use model factories for testing purposes. Create a new test for Posts by running the following command:

php artisan make:test PostsTest

It will create a new file at tests/Feature/PostsTest.php. Open this file and add a new method testPostsCreation. Here is the completed version of the file:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class PostsTest extends TestCase
{
    use DatabaseTransactions;

    public function testPostsCreation()
    {
        $title = 'Some subject!';
        $description = 'Some description';
        factory('App\Post')->create([
            'title' => $title,
            'description' => $description,
        ]);

        $this->assertDatabaseHas('posts', [
            'title' => $title,
            'description' => $description
        ]);
    }
}

In this test file, we are using DatabaseTransactions, so that we run each test wrapped in a transaction. Inside testPostsCreation we are creating a new Post model through its factory. In the create method we are passing an array with columns and its values which will override the stored data in the original model factory. So, it will create a post model with title and description set to our given values.

For testing, we are using assertDatabaseHas method which returns true if the given column and its values are present in the given table. The first argument is the table name and the second argument is the column name and its values passed as an array.

Factory make vs create method

There are two methods that we normally use on the factory helper function. One is make and the other one is create. Create tries to store data in the database like we save an eloquent model. On the other hand, make creates the model and returns it with its properties defined through the factory. If we have not defined any amount for the factory, we could chain the save method with the make and it would work the same way as create.

// Both of them works the same way
factory('App\User')->make()->save();
factory('App\User')->create();

But if we define any amount in the factory like factory('App\User', 5)->make(), it returns a collection of User models after building. We can also iterate through the collection and save each of them using the each method.

factory('App\User', 10)->make()->each(function($u) {
    return $u->save();
});

Working with States

You can also define factory states in Laravel. To do this let’s first adjust our migration for posts table. Add two new boolean columns is_featured and is_active. Here is the adjusted version of the up method in database/migrations/create_posts_table.php

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id');
        $table->string('title');
        $table->text('description');
        $table->boolean('is_featured')->default(false);
        $table->boolean('is_active')->default(false);
        $table->timestamps();
    });
}

Let’s say we want to define the ability to set is_featured and is_active to true. Define new states for is_active and is_featured in database/factories/PostFactory.php below the factory definition. Here is the code for states created:

$factory->state(App\Post::class, 'active', function() {
    return [
        'is_active' => true,
    ];
});

$factory->state(App\Post::class, 'featured', function() {
    return [
        'is_featured' => true,
    ];
});

In the Factory state, the first parameter is the class of the model, second is the name of the state and third is a callback function which returns some column name and its value.

With these states created, we can now call them like this:

// Create a new Post
factory('App\Post')->create();

// Create a new active post
factory('App\Post')->states('active')->create();

// Create a new featured post
factory('App\Post')->states('featured')->create();

// Create a new active featured post
factory('App\Post')->states('active', 'featured')->create();

When we are using active state, it will set active to true. Same for the featured state. We can also use multiple states by passing them as arguments in states method.

Creating Model Factories Automatically

Laravel Test Factory Helper (Github) is a package created by Marcel Pociot that generates model factories automatically from your existing models and database migrations. After you have installed it and added the service provider in your config/app.php, it gives you a new artisan command for generating model factories.

php artisan test-factory-helper:generate

This command will then create factories and save them in your database/factories/ModelFactory.php file. File needs to be present because it will not create a new file. Output filename is configurable by using the--filename option. This command will only append and it will not modify any existing data in your factories file. You could use --reset option that will rewrite the file.

I have also created a git repository for this project here: Laravel Model Factories.

You might also like