| name | spec-performance |
| description | This skill should be used when the user asks about "slow specs", "test performance", "parallel tests", "spec profiling", "let vs let!", "build vs create", "test optimization", or needs guidance on making RSpec tests faster. |
| version | 1.0.0 |
Spec Performance
Slow tests reduce productivity and discourage running tests frequently. This skill covers techniques for optimizing RSpec test suite performance.
Profiling Slow Tests
Built-in Profiler
rspec --profile 10
RSpec.configure do |config|
config.profile_examples = 10
end
Detailed Timing
RSpec.configure do |config|
config.around(:each) do |example|
start = Time.now
example.run
elapsed = Time.now - start
puts "#{example.full_description}: #{elapsed.round(2)}s" if elapsed > 1
end
end
Database Optimization
Prefer build Over create
let(:user) { create(:user) }
let(:user) { build(:user) }
let(:user) { build_stubbed(:user) }
When to Use Each Strategy
| Strategy | Use When |
|---|
build | Testing validations, object behavior |
build_stubbed | Need ID, testing presentation |
create | Testing DB queries, associations, callbacks |
Minimize Database Writes
it "lists users" do
create(:user)
create(:user)
create(:user)
expect(User.count).to eq(3)
end
it "lists users" do
User.insert_all([
{ email: "a@test.com" },
{ email: "b@test.com" },
{ email: "c@test.com" }
])
expect(User.count).to eq(3)
end
Use before(:all) Carefully
before(:all) do
@user = create(:user)
end
after(:all) do
@user.destroy
end
let vs let!
let - Lazy Evaluation
let(:user) { create(:user) }
it "does something" do
user.name
end
it "does something else" do
expect(true).to be true
end
let! - Eager Evaluation
let!(:user) { create(:user) }
it "has a user in database" do
expect(User.count).to eq(1)
end
When to Use let!
let!(:user) { create(:user) }
let!(:existing_user) { create(:user, email: "taken@test.com") }
it "validates uniqueness" do
new_user = build(:user, email: "taken@test.com")
expect(new_user).not_to be_valid
end
Avoid Unnecessary let!
let!(:user) { create(:user) }
let!(:post) { create(:post, user: user) }
let!(:comment) { create(:comment, post: post) }
let(:user) { create(:user) }
context "with posts" do
let(:post) { create(:post, user: user) }
it "lists posts" do
post
expect(user.posts).to include(post)
end
end
Parallel Testing
parallel_tests Gem
gem "parallel_tests", group: [:development, :test]
rake parallel:setup
rake parallel:create
rake parallel:migrate
rake parallel:spec
parallel_rspec spec/
Database Configuration
test:
database: myapp_test<%= ENV['TEST_ENV_NUMBER'] %>
Avoiding Parallelization Issues
let(:email) { "user#{Process.pid}@test.com" }
let(:file_path) { Rails.root.join("tmp/test_#{Process.pid}.txt") }
Mocking and Stubbing for Speed
Stub External Services
it "fetches data" do
result = ExternalApi.fetch(id: 1)
expect(result).to be_present
end
it "fetches data" do
allow(ExternalApi).to receive(:fetch).and_return({ data: "test" })
result = ExternalApi.fetch(id: 1)
expect(result).to eq({ data: "test" })
end
Use WebMock for HTTP
gem "webmock", group: :test
require "webmock/rspec"
WebMock.disable_net_connect!(allow_localhost: true)
stub_request(:get, "https://api.example.com/users")
.to_return(status: 200, body: { users: [] }.to_json)
Stub Time-Consuming Methods
it "processes file" do
result = FileProcessor.process(large_file)
expect(result).to be_present
end
it "processes file" do
allow(FileProcessor).to receive(:process).and_return({ success: true })
result = FileProcessor.process(large_file)
expect(result).to eq({ success: true })
end
Test Data Optimization
Use build_stubbed for Speed
user = build_stubbed(:user)
user.id
user.persisted?
Minimal Factory Attributes
factory :order do
user
shipping_address
billing_address
coupon
association :items, count: 5
end
factory :order do
status { "pending" }
total { 100 }
trait :with_user do
user
end
end
Avoid N+1 in Test Setup
let(:users) { create_list(:user, 10) }
before do
users.each { |u| create(:post, user: u) }
end
before do
users = create_list(:user, 10)
Post.insert_all(users.map { |u| { user_id: u.id, title: "Post" } })
end
CI Optimization
Fail Fast
RSpec.configure do |config|
config.fail_fast = ENV["CI"].present?
end
Bisect for Flaky Tests
rspec --bisect
Example Status Persistence
RSpec.configure do |config|
config.example_status_persistence_file_path = "spec/examples.txt"
end
Performance Checklist
Quick Wins
Medium Effort
Advanced
Benchmarking Helpers
module BenchmarkHelper
def measure(label = "Block", &block)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
result = block.call
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
puts "#{label}: #{(elapsed * 1000).round(2)}ms"
result
end
end
include BenchmarkHelper
it "performs quickly" do
measure("User creation") { create(:user) }
measure("User build") { build(:user) }
end