Better Integration Testing in Laravel 5.1: Model Factories

Posted on June 16, 2015 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

When you're testing your code, you often want to create a fake entry for one or more of your models. You may have tried this in your tests:

$post = new Post;
$post->title = 'Fake Blog Post Title';
$post->body = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lorem erat, luctus at diam sed, dapibus facilisis purus. In laoreet enim nunc, ut pretium arcu scelerisque in. Nunc eu cursus nibh. Etiam pulvinar vulputate libero sed molestie. In condimentum varius faucibus. Vestibulum non blandit sapien, quis tincidunt augue. Aliquam congue sapien eget mattis sagittis.';
$post->save();

That's already a pain to write out in the middle of your test. But what about when you need to create multiples? To differentiate them? Create related items? It gets out of control.

The factory

If you've ever used TestDummy or Faktory (or factory_girl in Ruby), you're already familiar with using a "factory" to build fake entities for your testing.

If you haven't used them before, these are libraries that make it simple to create a pattern for how to generate fake entities for testing. You're defining to the system: "Every time I request an entity of class type, give me one with these properties filled out to these values."

Note: It's worth pointing out that the fields you provide by default shouldn't always be "every field available to this model", but rather those which are useful in your context. Especially in testing, there are often circumstances where you only want to set a minimum set of fields.

Want to learn more about how factories work, especially in testing? Check out Adam Wathan's article introducing model factories for a better overview about how to use them.

Our first example

Let's take a look at an example. We'll use the Laravel 5.1 syntax:

// app/database/ModelFactory.php
$factory->define('App\Post', function () {
    return [
        'title' => 'My Awesome Post',
        'body' => 'The Body'
    ];
});

You've just defined that, every time someone requests an entity of class App\Post (and from now on, why don't we just write App\Post::class instead of 'App\Post'?) from the factory, they will receive a Post with title of My Awesome Post and a body of The Body. Pretty straightforward.

Using the make syntax

So, how do we request such an entity? Let's start with make():

$post = factory(App\Post::class)->make();

What do you think you're getting? An entity of class Post, with the same title and body every time.

dd($post->toArray());

/* Returns:
array:2 [
  "title" => "My Awesome Post"
  "body" => "The Body"
]
*/

Creating multiples

So, what if we want to create three objects for testing?

$posts = factory(App\Post::class, 3)->make();

Done. You now get a Collection of Posts.

dd($posts->toArray());

/* Returns:
array:3 [
  0 => array:2 [
    "title" => "My Awesome Post"
    "body" => "The Body"
  ]
  1 => array:2 [
    "title" => "My Awesome Post"
    "body" => "The Body"
  ]
  2 => array:2 [
    "title" => "My Awesome Post"
    "body" => "The Body"
  ]
]
*/

Using Faker

You've probably noticed that having three Posts with the same data is less useful than we might want.

Enter the Faker library. It's always been a great tool for seeding and it's at times useful in testing; it makes it simple to generate structured, fake data for your fake entities.

It's even easier now: Faker is baked into Laravel 5.1. Check it out:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph
    ];
});

Faking, built right in. Now every entity you create with the factory will have a unique title and body:

$posts = factory(App\Post::class, 3)->make();

dd($posts->toArray());

/* Returns:
array:3 [
  0 => array:2 [
    "title" => "Ea quis animi ex eius in aut."
    "body" => "Animi velit rerum corrupti quod nam consequuntur. Eius mollitia ut voluptatum laborum quod ex est. Id et aut aut molestias distinctio illo."
  ]
  1 => array:2 [
    "title" => "Illo quod doloribus placeat."
    "body" => "Ea dolorem eligendi modi sit. Facilis incidunt et sequi velit quia. Ab ipsa dicta dolor doloribus."
  ]
  2 => array:2 [
    "title" => "Quod qui ea et quo."
    "body" => "Iure atque vel rerum perspiciatis voluptatem eligendi provident molestiae. Porro aut est accusamus aut. Tempora quisquam ea delectus nihil hic quidem alias velit. Necessitatibus et illum quo culpa ad sint."
  ]
]
*/

Note: Faker is easily injectable by default, but if you want to use it, you still need to include it in your composer.json as fzaninotto/faker. Thanks to Eric Barnes for the tip!

Persisting to the database

Since we'll be using these model factories often in integration testing (or in testing database-backed chunks of the application), we're going to need to talk about how to persist these fake entities to the database. Thankfully, it's simple:

factory(App\Post::class, 20)->create();

That's it! Now you have 20 fake Posts in your database.

A few practical examples

First, these work great in your database seeders. Just truncate the database and run factory(ClassName::class, 20)->create(); and you're seeded and ready to go.

But let's take a look at a testing example.

<?php
...
class PostRepositoryTest extends TestCase
{
    public function test_it_paginates()
    {
        factory(App\Post::class, 50)->create();

        $thisPage = (new App\PostRepository)->paginate();

        $this->assertEquals(20, $thisPage->count());
    }
}

I wrote earlier that these factories are good for integration testing, and they are; but as you can see above, they're also good for testing any smaller pieces (unit or functional) that access the database.

We inserted more than our pagination count, ran the all() method, and tested our return. This test could only function properly with real data in a database, unless you are interested in mocking your entire Eloquent database access layer, which I wouldn't recommend.

Here's a simple integration testing example:

<?php
...
class PostListPageTest extends TestCase
{
    public function test_list_page_paginates()
    {
        factory(App\Post::class, 50)->create();

        $this->visit('/posts')
            ->see('Next Page');
    }
}

We are only expecting the "Next Page" button to show up if there are more than our pagination count (20), so we added 50 and checked to see that the posts page shows the "Next Page" button.

Another option is to actually look at our individual items:

<?php
...
class PostListPageTest extends TestCase
{
    public function test_list_page_shows_titles()
    {
        $post = factory(App\Post::class)->create();

        $this->visit('/posts')
            ->see($post->title);
    }
}

Customizing the output of your models

What if you're working with a particular model, and you want to set a particular value on it for testing purposes?

Just pass the override parameters as an array to the make/create method:

$user = factory(App\Post::class)->make([
    'title' => 'THE GREATEST POST',
]);

Defining multiple types for a single model

What if you need to generate two different sorts of Post to test a particular condition against others?

Try defineAs to allow yourself to specific a 'type' when you're generating fake entities. You can either create the two entirely separately:

$factory->defineAs(App\Post::class, 'short-post', function ($faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph
    ];
});

$factory->defineAs(App\Post::class, 'long-post', function ($faker) {
    return [
        'title' => $faker->sentence,
        'body' => implode("\n\n", $faker->paragraphs(10))
    ];
});

Or, you can extend the base type with a customized type:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph
    ];
});

$factory->defineAs(App\Post::class, 'long-post', function ($faker) use ($factory) {
    $post = $factory->raw('App\Post');

    return array_merge($post, ['body' => implode("\n\n", $faker->paragraphs(5))]);
});

Adding relationships to your models

Leverage the magic of Illuminate Collections to quickly set up your fake entities and their relationships:

$posts = factory('App\Post', 3)
    ->create()
    ->each(function($post) {
        $post->relatedItems()->save(factory('App\Item')->make());
    });

Conclusion

Model factories have long been a powerful tool to aid testing and seeding. Having them built-in to the framework is now one less step in the way of us testing well and consistently.

Check back later this week for even more 5.1 testing goodies.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.1  •  testing  •  integration testing