Oct 28, 2025 rails · testing · performance · fixtures · factorybot

How we cut our Rails test suite setup time by 95%

Ever stared at your test suite taking forever and thought “there’s gotta be a better way”? Yeah, me too.

We had API specs taking 1 minute and 40 seconds to run. The culprit? Creating 50+ blog categories through FactoryBot for every single test. Each one triggering expensive slug generation, parent-child hierarchy calculations, and enough ActiveRecord callbacks to make your CPU weep.

What’s Killing Your Test Suite

Most Rails apps treat all test data the same - everything goes through FactoryBot. But here’s the thing: not all test data is created equal.

You’ve got two types: - Static reference data - categories, tags, author profiles that rarely change - Dynamic test data - posts, comments, user sessions that vary per test

We were recreating the same 50+ category records over and over. That’s just wasteful.

Here’s How We Fixed It

Use Rails fixtures for the expensive static stuff, keep FactoryBot for everything else.

Look for data that’s expensive to create and needed everywhere:

# This was killing us - 50+ categories with complex nesting
def load_blog_categories
  create_category("Ruby", parent: nil) do |ruby|
    create_category("Rails", parent: ruby)
    create_category("Testing", parent: ruby)
    create_category("Performance", parent: ruby)
    # ... 50+ more with expensive slug generation and callbacks
  end
end

Create YAML files instead:

# spec/fixtures/categories.yml
ruby_root:
  name: "Ruby"
  slug: "ruby"
  parent_id:

rails_category:
  name: "Rails"
  slug: "ruby/rails"
  parent: ruby_root

testing_category:
  name: "Testing"
  slug: "ruby/testing"
  parent: ruby_root

Replace expensive object creation:

# Before: Individual creation with callbacks
def load_blog_structure
  load_categories
  load_tags
  load_author_profiles
  # ... 8 more expensive methods
  Category.rebuild_slugs! # Ouch
end

# After: Bulk fixture loading
def load_blog_structure_from_fixtures
  ActiveRecord::FixtureSet.create_fixtures(
    Rails.root.join("spec/fixtures"),
    [:categories, :tags, :author_profiles]
  )
end

The Secret Sauce Behind Fixtures

Rails fixtures skip the expensive stuff: - No ActiveRecord callbacks - Bulk SQL inserts instead of individual ones - Clean reference syntax with automatic foreign key resolution

The Numbers Don’t Lie

From 1m 40s to 7s. That’s 95% faster.

Your Decision Matrix

Use fixtures for: Reference data, complex hierarchical data, anything with expensive callbacks

Keep FactoryBot for: Test-specific data, data that varies per test, small datasets

Making It Work in Practice

Don’t convert everything at once. Profile your slowest tests, extract static data first, keep both approaches during transition.

Sometimes you need both:

FactoryBot.define do
  factory :post do
    title { "Understanding Ruby Metaprogramming" }
    # Reference fixture data for expensive stuff
    category { Category.find_by(slug: "ruby/rails") }
    # Use factories for test-specific data
    author { create(:author) }
  end
end

The Bottom Line

The 80/20 rule applies here. 80% of your test performance problems come from 20% of your data creation.

Find that 20% and fix it with fixtures. Keep factories for everything else.

Profile first, be strategic, measure results. Fast tests = faster development cycles.

What’s your biggest test performance bottleneck?


Disclaimer: the code examples have been fictionalized for illustrative purposes, but the gist of the article and the numbers stem from a real project.