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?