Creating test data with factories [1d]

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:

Tip

You will be calling your factories a lot. Therefore it is preferrable to just say create(:movie) instead of FactoryBot.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 new Actor 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 and before/after hooks to implement the factory above.

Henning Koch Over 2 years ago