Home > PHP Framework > Laravel > body text

How to use model factory in Laravel application?

青灯夜游
Release: 2022-11-28 20:26:22
forward
1170 people have browsed it

How to use model factory in Laravel application? The following article will introduce to you how to use Laravel model engineering in testing. I hope it will be helpful to you!

How to use model factory in Laravel application?

#Laravel Model Factory is one of the best features you can use while testing in your application. They provide a way to define data that is predictable and easily replicable so that your tests remain consistent and controllable.

Let's start with a simple example. We have an application for blogging, so naturally we have a Post model that has a status of Published, Drafted, or Queued. Let's take a look at the Eloquent model for this example:

declare(strict_types=1);

namespace App\Models;

use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Model;

class Post extends Model
{
    protected $fillable = [
        'title',
        'slug',
        'content',
        'status',
        'published_at',
    ];

    protected $casts = [
        'status' => PostStatus::class,
        'published_at' => 'datetime',
    ];
}
Copy after login

As you can see here, we have an Enum for the status column, which we will now design. Using enums here allows us to take advantage of PHP 8.1 features instead of plain strings, boolean flags, or confusing database enums.

 declare(strict_types=1);

namespace App\Publishing\Enums;

enum PostStatus: string
{
    case PUBLISHED = 'published';
    case DRAFT = 'draft';
    case QUEUED = 'queued';
}
Copy after login

Now, let’s get back to the topic we’re discussing here: Model Factory. A simple factory looks simple:

 declare(strict_types=1);

namespace Database\Factories;

use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        $title = $this->faker->sentence();
        $status = Arr::random(PostStatus::cases());

        return [
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => $this->faker->paragraph(),
            'status' => $status->value,
            'published_at' => $status === PostStatus::PUBLISHED
                ? now()
                : null,
        ];
    }
}
Copy after login

So in our tests we can now quickly call our post factory to create a post for us. Let's see how we can do this:

 it('can update a post', function () {
    $post = Post::factory()->create();

    putJson(
        route('api.posts.update', $post->slug),
        ['content' => 'test content',
    )->assertSuccessful();

    expect(
        $post->refresh()
    )->content->toEqual('test content');
});
Copy after login
Copy after login

A simple enough test, but what happens if our business rules state that you can only update specific columns based on post type? Let's refactor our test to make sure we can do this:

it('can update a post', function () {
    $post = Post::factory()->create([
        'type' => PostStatus::DRAFT->value,
    ]);

    putJson(
        route('api.posts.update', $post->slug),
        ['content' => 'test content',
    )->assertSuccessful();

    expect(
        $post->refresh()
    )->content->toEqual('test content');
});
Copy after login

Perfectly, we can pass a parameter to the create method to make sure we set the correct type when we create it, so that our The business rules won't complain. But writing it like this is a bit cumbersome, so let's refactor our factory a little and add a method to modify the status:

 declare(strict_types=1);

namespace Database\Factories;

use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        $title = $this->faker->sentence();

        return [
            'title' => $title,
            'slug' => Str::slug($title),
            'content' => $this->faker->paragraph(),
            'status' => PostStatus::DRAFT->value,
            'published_at' => null,
        ];
    }

    public function published(): static
    {
        return $this->state(
            fn (array $attributes): array => [
                'status' => PostStatus::PUBLISHED->value,
                'published_at' => now(),
            ],
        );
    }
}
Copy after login

We set a default value for the factory so that all newly created posts are drafts. We then add a method to set the state to publish, which will use the correct Enum value and set the publish date - more predictable and repeatable in a test environment. Let's see what our test looks like now:

 it('can update a post', function () {
    $post = Post::factory()->create();

    putJson(
        route('api.posts.update', $post->slug),
        ['content' => 'test content',
    )->assertSuccessful();

    expect(
        $post->refresh()
    )->content->toEqual('test content');
});
Copy after login
Copy after login

Back to a simple test - so if we have multiple tests that want to create a draft post, they can use a factory. Now let's write a test for the published state to see if there are any errors.

 it('returns an error when trying to update a published post', function () {
    $post = Post::factory()->published()->create();

    putJson(
        route('api.posts.update', $post->slug),
        ['content' => 'test content',
    )->assertStatus(Http::UNPROCESSABLE_ENTITY());

    expect(
        $post->refresh()
    )->content->toEqual($post->content);
});
Copy after login

This time we are testing whether we receive a validation error status when we try to update a published post. This ensures we protect our content and enforce specific workflows within our applications.

So what happens if we also want to ensure specific content in the factory? We can add another method to modify the status if needed:

 declare(strict_types=1);

namespace Database\Factories;

use App\Models\Post;
use App\Publishing\Enums\PostStatus;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class PostFactory extends Factory
{
    protected $model = Post::class;

    public function definition(): array
    {
        return [
            'title' => $title = $this->faker->sentence(),
            'slug' => Str::slug($title),
            'content' => $this->faker->paragraph(),
            'status' => PostStatus::DRAFT->value,
            'published_at' => null,
        ];
    }

    public function published(): static
    {
        return $this->state(
            fn (array $attributes): array => [
                'status' => PostStatus::PUBLISHED->value,
                'published_at' => now(),
            ],
        );
    }

    public function title(string $title): static
    {
        return $this->state(
            fn (array $attributes): array => [
                'title' => $title,
                'slug' => Str::slug($title),
            ],
        );
    }
}
Copy after login

So within our tests we can create a new test to make sure we can update the draft post title via our API:

 it('can update a draft posts title', function () {
    $post = Post::factory()->title('test')->create();

    putJson(
        route('api.posts.update', $post->slug),
        ['title' => 'new title',
    )->assertSuccessful();

    expect(
        $post->refresh()
    )->title->toEqual('new title')->slug->toEqual('new-title');
});
Copy after login

So we can use factory state nicely to control things in our test environment, giving us as much control as possible. Doing so will ensure that we are consistently prepared for testing, or that the state of the application at a specific point is well reflected.

What should we do if we need to create many models for our tests? What should we do? The simple answer is to tell the factory:

it('lists all posts', function () {
    Post::factory(12)->create();

    getJson(
        route('api.posts.index'),
    )->assertOk()->assertJson(fn (AssertableJson $json) =>
        $json->has(12)->etc(),
    );
});
Copy after login

So we are creating 12 new posts and making sure that when we get the index route, we have 12 posts returned. Instead of passing count to the factory method, you can also use the count method:

Post::factory()->count(12)->create();
Copy after login

However, sometimes in our application we may want to run things in a specific order. Suppose we want the first one to be a draft, but the second one to be published?

 it('shows the correct status for the posts', function () {
    Post::factory()
        ->count(2)
        ->state(new Sequence(
            ['status' => PostStatus::DRAFT->value],
            ['status' => PostStatus::PUBLISHED->value],
        ))->create();

    getJson(
        route('api.posts.index'),
    )->assertOk()->assertJson(fn (AssertableJson $json) =>
        $json->where('id', 1)
            ->where('status' PostStatus::DRAFT->value)
            ->etc();
    )->assertJson(fn (AssertableJson $json) =>
        $json->where('id', 2)
            ->where('status' PostStatus::PUBLISHED->value)
            ->etc();
    );
});
Copy after login

How do you use model factories in your application? Have you found any cool ways to use them? Tell us on twitter!

Original address: https://laravel-news.com/laravel-model-factories

Translation address: https://learnku.com/laravel/t/70290

[Related recommendations: laravel video tutorial]

The above is the detailed content of How to use model factory in Laravel application?. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:learnku.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template