Goals
- Learn to create test data effectively using factories.
- Decouple tests by having each test start with an empty database and create only the records needed for the test.
Learn
Factories, not fixtures
By default Rails uses global fixtures Show archive.org snapshot for its tests. This is a giant world of example data that grows with every test.
In our experience the use of fixtures can make a test suite hard to work with. In any non-trivial test suite there will be thousands of invisible dependencies between fixtures and test examples. E.g. to test a new edge case you make a small change to an existing fixture. Then a dozen tests break because they relied on the data you just changed.
Getting to know FactoryBot
Our favorite gem for test factories is FactoryBot Show archive.org snapshot . We used other gems in the past, but they all work in the same way.
Watch Railscasts PRO #158 Factories not Fixtures (revised) Show archive.org snapshot for an introduction to factories in general and FactoryBot in particular.
Note
This is an older video. FactoryGirl has since been renamed to FactoryBot.
Read the FactoryBot Getting Started guide Show archive.org snapshot . Despite its name this is an in-depth guide and covers advanced use cases.
Factories should not be random
You may encounter libraries like Faker Show archive.org snapshot that creates realistic names, addresses, etc. for your sample data. This looks awesome, but at the risk of adding random strings to your screen.
Read don't build randomness into your factories for more background.
Tests should never influence each other
On a similar note, we don't want tests to behave differently based on their execution order or parallelism. One core mechanism in this regard is a gem called Database Cleaner Show archive.org snapshot . It ensures that every test starts with an empty database by erasing the test database before every unit and integration test.
Have a look at its documentation. The gem is already part of your MovieDB.
Exercises
Setup FactoryBot
If you haven't done so already:
- Install FactoryBot via
factory_bot_rails
Show archive.org snapshot .
Tip
You will be calling your factories a lot. Therefore it is preferrable to just say
create(:movie)
instead ofFactoryBot.create(:movie)
. You can configure this like so:RSpec.configure do |config| config.include FactoryBot::Syntax::Methods end
Also see RSpec: Where to put custom matchers and other support code.
Use factories everywhere
- Write a factory for each of your MovieDB models.
- In your model specs and E2E features, only use factories to create your test data.
- Leverage that factories will set defaults for attributes that you don't explicitly mention. You should remove any attribute values that are not relevant to a test. E.g. if a test needs a
Movie
but does not care about its#year
attribute, the test should let the factory fill in a default year. However, if the test checks for one particular year, it should be explicitly set in the test (even though there is a factory default).
Advanced factory
Create a factory that works like this:
create(:movie, :biography, person: 'Al Gore')
This creates up to three records (class names may differ in your MovieDB implementation):
- A
Movie
with with title"Al Gore: Biography"
- An
Actor
with the name"Al Gore"
. If an actor with that name already exists, the record is re-used and no newActor
is created. - A
Role
that links the movie and actor above. The role's#character_name
should also be"Al Gore"
, since he would be playing himself.
Tip
Don't actually give your
Movie
model a#person
attribute.
Instead, use FactoryBot's transient attributes andbefore
/after
hooks to implement the factory above.