Laravel — P30: Factories

Efficient Testing in Laravel: The Role of Factories

If you’re just starting out with Laravel, some of the first few questions that you’ll come across are:

  • What is the difference between Seeders, Fakers, and Factories?
  • Should I use them?
  • Do I need to use all of them or can I use some of them?
  • How do I create them?

Factories

According to Laravel’s official documentation, “factories are classes that extend Laravel’s base factory class and define a model property and definition method.” Just think about it this way. If you’ve ever created a model, like the User model (App\Models\User), you’ll know that certain fields should be populated when you use the create() method.

<?php

User::create([
    'name' => $input['name'],
    'email' => $input['email'],
    'password' => Hash::make($input['password']),
]);

I know that we haven’t covered the create() method yet. In the previous article, we simply created a new instance of a class and then added the properties to it. You can still do that, or you can use the create() method, pass an associative array to it, where the keys match the column names in the table that you’re working with (in this case, the users table), and pass the values for each of those elements.

In the example above, we provide the nameemail, and password fields to the User model to populate the users table. A factory will just define a standard way for us to do this. This is incredibly helpful when you’re testing your application.

In your tests, you might have hundreds of places where you have to generate a new user. If you ever change your code, you’ll have to change it in hundreds of different places (for example, adding roles to your users). You’re just creating a test user so you can leverage a factory where it will create that test user for you.

Laravel has already created a User factory for us. It’s located in database/factories/UserFactory.php.

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition()
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     *
     * @return static
     */
    public function unverified()
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

The UserFactory utilizes Faker, which we won’t use in this example. Let’s modify the code in the definition() method to use a static name and email.

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => "Dino Cajic",
            'email' => "dino@example.com",
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }

    // ...
}

You can see that the definition() method just returns an array…almost like the array that we can pass to the create() method in our User model.

To call a factory, we can “use the static factory method provided by theIlluminate\Database\Eloquent\Factories\HasFactorytrait on our Eloquent model.” In our case, User::factory()->make().

How does the User model know which factory it should use? The model has the trait, HasFactory. The trait looks inside of the Database\Factories namespace. It there exists a factory with the same name as the model name, and the word Factory appended to it, Laravel assumes that this is the factory for it. For example, the User model should have a factory called UserFactory inside of the Database\Factories namespace.

How to Test Our Factory?

Before we get into seeders, we can use another artisan command line tool called tinker. Tinker allows you to play around with your code (tinker with it). To run the command, type in php artisan tinker. This will allow you to run PHP/Laravel commands.

Since I’m running a docker instance, inside of my Laravel container, I’ll run the following command:

# php artisan tinker
Psy Shell v0.11.9 (PHP 8.1.13 — cli) by Justin Hileman
>

I can now enter a command like: User::factory()->make();.

# php artisan tinker
Psy Shell v0.11.9 (PHP 8.1.13 — cli) by Justin Hileman
> User::factory()->make();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= App\Models\User {#3803
    name: "Dino Cajic",
    email: "dino@example.com",
    email_verified_at: "2023-01-14 17:08:23",
    #password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
    #remember_token: "8G9onKzAnB",
  }

> 

I can see that the command was executed successfully and we even got a response.

But what does this do? This creates the User model; it doesn’t actually save it to the database. To save it to the database, you can use create() instead of make(). See how I’m just sliding new commands in there for you. Don’t worry, we’ll deep dive into models later, but there are a few that are pretty self explanatory.

> User::factory()->create();
= App\Models\User {#3809
    name: "Dino Cajic",
    email: "dino@example.com",
    email_verified_at: "2023-01-14 17:10:19",
    #password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
    #remember_token: "ONtAKjJsOP",
    updated_at: "2023-01-14 17:10:19",
    created_at: "2023-01-14 17:10:19",
    id: 1,
  }

> 

You’ll notice that we now have an id and updated_at/created_at timestamps that came directly from the database.

You can verify that the user exists by looking at the users table in your database. I’ll quickly jump into my mysql container and select everything from the users table.

mysql> select * from users;
+----+------------+---------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
| id | name       | email               | email_verified_at   | password                                                     | remember_token | created_at          | updated_at          |
+----+------------+---------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
|  1 | Dino Cajic | dino@example.com | 2023-01-14 17:10:19 | $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi | ONtAKjJsOP     | 2023-01-14 17:10:19 | 2023-01-14 17:10:19 |
+----+------------+---------------------+---------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+
1 row in set (0.00 sec)

mysql>

But, we also have tinker. We can run the User::all() command in tinker to retrieve all of the users from the users table.

> User::all();
= Illuminate\Database\Eloquent\Collection {#3796
    all: [
      App\Models\User {#3793
        id: 1,
        name: "Dino Cajic",
        email: "dino@example.com",
        email_verified_at: "2023-01-14 17:10:19",
        #password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
        #remember_token: "ONtAKjJsOP",
        created_at: "2023-01-14 17:10:19",
        updated_at: "2023-01-14 17:10:19",
      },
    ],
  }

> 

And that’s it. When you’re creating your tests, you can use the factory command to create any new instance of a user wherever you need it.

This approach will create a new user with the same name and email each time. You’ll only be able to run the command once since the email has to be unique. What if you wanted a random name and a unique email each time? That’s where faker comes in. We’ll look at it next.

https://github.com/dinocajic/youtube-laravel

Laravel Series

Continue your Laravel Learning.

Laravel — P29: Models Intro

Laravel Models Unveiled: Your Gateway to Data Interaction

Laravel – P29: Models

Dive into Laravel models to interact with database effortlessly. Discover how to utilize models for efficient data handling & retrieval.

Laravel — P30: Factories

Efficient Testing in Laravel: The Role of Factories

Laravel – P30: Factories

Master Laravel factories for rapid testing & data seeding. Learn how to create realistic data models for development & testing efficiency.

Laravel — P31- Faker

Enhancing Laravel Data with Faker: Realistic Testing Made Easy

Laravel – P31: Faker Library

Elevate your Laravel testing with Faker for realistic data generation. Streamline development with dynamic, believable test data.

Leave a Reply