Now that we know how to create tests, it’s time we looked at how we can use factories to generate test data quickly and easily.
Why We Should Use Seeders
Let’s start with an example to work through why we need seeders. We’re going to create a new piece of logic for our
Project class and say that if a
Project has a
end_date column then it hasn’t been completed. To test this we’re going to create a simple unit test (in
Tests\Unit\ProjectTest) that looks like the following:
If we run this we’ll get an error indicating that the function doesn’t exist. When we create the function we could just stick in a
return false and call it good for this phase of the TDD cycle but the implementation for this function is so trivial we’re just going to write it directly:
When we run the tests again we’ll be back to a green bar. Now to check the opposite.
Great! But now we have a lot of duplication between the two tests. The first three lines are identical and the forth is almost identical so we can extract a function and reduce some duplication:
We can be happy with this solution and check it into our SCM but now if we need to initialize a
Project in another test we’ll have to do the same basic steps. We could add the function to
Tests\TestCase but Laravel provides a better solution.
We going to use
make:factory command to generate our factory for us.
The general format for this command is:
For our example, we’re going to specify “Project” as the “ModelName”.
Now we can look in “database/factories/ProjectFactory.php” and see the following code:
To get us started we’re going to pull in the code we created in the
createNotCompletedProject() function above.
Now that we’ve created our factory we can revisit
Tests\Unit\ProjectTest and see how the factory affects what we’ve come up with. We’ll replace the lines containing
$item = $this->createNotCompletedProject(); with
$item = factory(Project::class)->make();. Then we can delete
That looks a lot better.
When we setup our factory we set the
Project’s name to “Some Name” which is quick way to get it setup but all of our
Projects will have the same name.
Luckily, Laravel comes with the Faker library installed and setup. By default it’s passed as a parameter to our factories (note the
The Faker library allows you to easily create test data that isn’t all identical. Its GitHub page (https://github.com/fzaninotto/Faker) has a complete list of all the values you can use but some of interest include:
In our example we’re going to have it pick the name of a company for us to use.
Now if we run our
dd() test from above we get a company name that will change on every test.
Projects at once
There’s an optional parameter to the
factory() helper function that tells it to return an array of the model instead of a single instance. This is helpful if you need to act on several copies of a model.
Let’s say we need to initialize several completed
Projects quickly. We can use that optional parameter we discussed above to generate them and then loop through them to in order to set their
This is a little cumbersome so thankfully Laravel has a better solution for us. Factory states allow us to define another set of changes that should be applied to the object create using the factory by adding values on top of the “defaults” set by the base factory.
If we open up our factory file for the
Project class we can add a state function that explains how to create a “completed”
Now we can revisit our tests that explicitly set the
end_date and apply our completed state to them.
In this example it doesn’t clean up a lot of code but if we had to set two or more properties it would really help.
We can also setup
Projects that have multiple states at once. For example, we can create a “startedLastYear” state that sets
startDate to the current date minus a year and apply “startedLastYear” and “completed” in one function call.
Overriding the Defaults
Sometimes it’s necessary to override one of the values inside the factory. This can be done by passing an associative array of the items you want changed.
Using Factories in Other Factories
Let’s say you have something like our
Task class which requires a parent
Project and the
User who created it. If we just create a factory that looks like the following. Then it won’t work.
We’ll get an error like this.
PDOException: SQLSTATE[HY000]: General error: 1364 Field ‘user_id’ doesn’t have a default value
To fix this problem we can use the factories defined for the
Project classes to automatically create the required objects.
In conclusion, factories are a powerful tool that allows you to quickly create objects to perform tests on. They reduce duplicate code and make it easier to setup your tests.
Hopefully this has been as helpful to you as it has to me and check back soon for more artisan commands.
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around his house with his loving partner.