Sep 18, 2025 rails · testing · performance · fixtures · factorybot

This One Weird Trick Cut Our Rails Test Suite Time by 95% (FactoryBot Fans HATE It!)

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 121 interconnected taxons through FactoryBot for every single test. Each one triggering expensive nested set calculations, permalink generation, 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 - taxonomies, categories, sizes, colors that rarely change - Dynamic test data - users, orders, t-shirts that vary per test

We were recreating the same 121 taxonomy 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 - 121 taxons with complex nesting
def load_categories_taxonomy
  with_taxonomy("Category", relative_permalink: true) do |b|
    b.create_taxon("Men's T-Shirts")
    b.create_taxon("Women's T-Shirts")
    # ... 100+ more with expensive callbacks
  end
end

Create YAML files instead:

# spec/fixtures/spree_taxons.yml
category_root:
  name: "Category"
  permalink: "category"
  taxonomy: category

mens_tshirts:
  name: "Men's T-Shirts"
  permalink: "category/mens-tshirts"
  taxonomy: category
  parent: category_root

Replace expensive object creation:

# Before: Individual creation with callbacks
def load_taxonomies
  load_size_taxonomy
  load_categories_taxonomy
  # ... 8 more expensive methods
  Spree::Taxon.rebuild! # Ouch
end

# After: Bulk fixture loading
def load_taxonomies_from_fixtures
  ActiveRecord::FixtureSet.create_fixtures(
    Rails.root.join("spec/fixtures"),
    [:spree_taxonomies, :spree_taxons]
  )
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 :product do
    name { "Test T-Shirt" }
    # Reference fixture data for expensive stuff
    category { Spree::Taxon.find_by(permalink: "category/mens-tshirts") }
    # Use factories for test-specific data
    designer { create(:designer) }
  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?