What the F*ck Is With All the Artisan Commands: Factories
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 null
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.
Enter Factories
We going to use artisan
’s 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 createNotCompletedProject()
.
That looks a lot better.
Using Faker
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 Project
s 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 Faker
below).
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.
Building Multiple Project
s 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.
States
Let’s say we need to initialize several completed Project
s quickly. We can use that optional parameter we discussed above to generate them and then loop through them to in order to set their end_date
.
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” Project
.
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 Project
s 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 User
and Project
classes to automatically create the required objects.
Conclusion
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 Keck-Warren
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 the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows