RSpec Power π₯
A powerful collection of RSpec helpers and utilities that supercharge your Rails testing experience! π
β¨ Features
| Feature | Summary | Usage |
|---|---|---|
| π Enhanced Logging | Capture and control Rails logs; ActiveRecord-only option |
:with_log, :with_logs, :with_log_ar, with_logging, with_ar_logging
|
| π Environment Management | Override environment variables with auto-restore |
:with_env, with_test_env
|
| π I18n Testing | Switch locales and assert translations |
:with_locale, with_locale
|
| β° Time Freeze | Freeze/travel time for deterministic tests | :with_time_freeze |
| π Time Zone | Run examples in a specific time zone | :with_time_zone |
| β‘ Performance Budgeting | Enforce maximum example execution time |
with_maximum_execution_time, :with_maximum_execution_time
|
| π Benchmarking | Run examples multiple times and summarize | with_benchmark: { runs: N } |
| π CI Guards | Conditionally run or skip on CI |
:with_ci_only, :with_skip_ci
|
| π§ͺ SQL Guards | Ensure no SQL or require at least one |
expect_no_sql, :with_no_sql_queries, expect_sql, :with_sql_queries
|
| πΎ Request Dump | Dump session, cookies, flash, headers after each example |
:with_request_dump, with_request_dump: { what: [:session, :cookies, :flash, :headers] }
|
| π’ DB Dump on Failure | Dump DB tables to CSV when an example fails |
:with_dump_db_on_fail, with_dump_db_on_fail: { tables: [...], except: [...] }
|
π¦ Installation
Add this line to your application's Gemfile:
group :test do
gem "rspec_power"
endAnd then execute:
bundle installIf you are using the "timecop" gem, you need to
- add to Gemfile as
gem "rspec_power", require: false - add
require "rspec_power"beforerequire "timecop"inrails_helper.rb.
π Quick Start
The gem automatically configures itself when required. Just add it to your Gemfile.
π Usage Examples
π Enhanced Logging
Capture all Rails logs during specific tests, copied from test-prof gem with a small enhancement:
RSpec.describe User, :with_log do
it "creates a user with logging" do
# All Rails logs will be captured and displayed
user = User.create!(name: "John Doe", email: "john@example.com")
expect(user).to be_persisted
end
endor for specific tests (alias :with_logs is also supported):
RSpec.describe User do
it "creates a user with logging", :with_log do
# All Rails logs will be captured and displayed
user = User.create!(name: "John Doe", email: "john@example.com")
expect(user).to be_persisted
end
endCapture only ActiveRecord logs:
RSpec.describe User, :with_log_ar do
it "shows SQL queries" do
# Only ActiveRecord logs will be captured
users = User.where(active: true).includes(:profile)
expect(users).to be_any
end
endManual logging control:
RSpec.describe User do
it "manually controls logging" do
with_logging do
# All Rails logs captured here
User.create!(name: "Jane")
end
# Logging back to normal here
end
it "captures only ActiveRecord logs" do
with_ar_logging do
# Only ActiveRecord logs captured here
User.count
end
end
endπ§ͺ SQL Guards
Ensure a block performs no SQL:
RSpec.describe CacheWarmup do
it "does not hit the DB" do
expect_no_sql do
CacheWarmup.build_in_memory!
end
end
endRequire that a block performs at least one SQL statement:
RSpec.describe MigrationChecker do
it "touches the DB" do
expect_sql do
ActiveRecord::Base.connection.execute("SELECT 1")
end
end
endOr tag an example/group to enforce or require queries automatically:
RSpec.describe CacheWarmup, :with_no_sql_queries do
it "builds entirely in memory" do
CacheWarmup.build_in_memory!
end
end
RSpec.describe MigrationChecker, :with_sql_queries do
it "must hit the DB at least once" do
ActiveRecord::Base.connection.execute("SELECT 1")
end
end
Supported tags: `:with_no_sql_queries`, `:with_sql_queries`π Environment Variable Management
Override environment variables for specific tests:
RSpec.describe PaymentService, :with_env do
it "uses test API key", with_env: { 'STRIPE_API_KEY' => 'test_key_123' } do
service = PaymentService.new
expect(service.api_key).to eq('test_key_123')
end
it "handles multiple env vars", with_env: {
'RAILS_ENV' => 'test',
'DATABASE_URL' => 'postgresql://localhost/test_db'
} do
expect(ENV['RAILS_ENV']).to eq('test')
expect(ENV['DATABASE_URL']).to eq('postgresql://localhost/test_db')
end
endManual environment control:
RSpec.describe ConfigService do
it "manually overrides environment" do
with_test_env('API_URL' => 'https://api.test.com') do
expect(ENV['API_URL']).to eq('https://api.test.com')
end
# Environment restored automatically
end
endπ Internationalization (I18n) Testing
Test your application in different locales:
RSpec.describe User, :with_locale do
it "displays name in English", with_locale: :en do
user = User.new(name: "John")
expect(user.greeting).to eq("Hello, John!")
end
it "displays name in Spanish", with_locale: :es do
user = User.new(name: "Juan")
expect(user.greeting).to eq("Β‘Hola, Juan!")
end
it "displays name in French", with_locale: :fr do
user = User.new(name: "Jean")
expect(user.greeting).to eq("Bonjour, Jean!")
end
endManual locale control:
RSpec.describe LocalizationHelper do
it "manually changes locale" do
with_locale(:de) do
expect(I18n.locale).to eq(:de)
expect(t('hello')).to eq('Hallo')
end
# Locale restored automatically
end
endβ° Time Freeze
Freeze time for consistent test results:
RSpec.describe Order, :with_time_freeze do
it "creates order with current timestamp", with_time_freeze: "2024-01-15 10:30:00" do
order = Order.create!(amount: 100)
expect(order.created_at).to eq(Time.parse("2024-01-15 10:30:00"))
end
it "handles time-sensitive logic", with_time_freeze: Time.new(2024, 12, 25, 12, 0, 0) do
expect(Time.current).to eq(Time.new(2024, 12, 25, 12, 0, 0))
# Test Christmas-specific logic
end
endπ Time Zone
RSpec.describe ReportGenerator do
it "builds report in US Pacific", with_time_zone: "Pacific Time (US & Canada)" do
# The block runs with Time.zone set to Pacific
expect(Time.zone.name).to eq("Pacific Time (US & Canada)")
end
endβ‘ Performance Budgeting
Limit example duration:
RSpec.describe Importer do
it "is fast enough" do
with_maximum_execution_time(50) do
Importer.run!
end
end
endOr via metadata:
RSpec.describe Importer, with_maximum_execution_time: 100 do
it "completes quickly" do
Importer.run!
end
endπ Benchmarking
Benchmark entire examples via metadata and get a suite summary:
RSpec.describe Parser, with_benchmark: { runs: 10 } do
it "parses quickly" do
Parser.parse!(payload)
end
endThe example is executed multiple times (runs) and the average/min/max times are printed after the suite.
πΎ Request Dump
Dump request-related state after each example to help debug request specs.
Supported items:
:session:cookies:flash:headers
Enable for an example or group and choose what to dump:
RSpec.describe "Users API", type: :request do
it "dumps everything by default", :with_request_dump do
post "/set_state"
expect(response).to be_successful
end
it "dumps only session and cookies",
with_request_dump: { what: [:session, :cookies] } do
post "/set_state"
expect(response).to be_successful
end
endExample output:
[rspec_power] Dump after example: Users API dumps everything by default
[rspec_power] session: {"user_id"=>42}
[rspec_power] cookies: {"hello"=>"world"}
[rspec_power] flash: {"notice"=>"done"}
[rspec_power] headers: { ... }
π CI Guards
Run or skip specs depending on whether the suite is running on CI.
- Tag to run only on CI:
:with_ci_only - Tag to skip on CI:
:with_skip_ci
RSpec.describe Deployment, :with_ci_only do
it "runs only on CI" do
expect(ENV["CI"]).to be_present
end
end
RSpec.describe HeavySpec, :with_skip_ci do
it "skips on CI" do
# expensive checks
end
endCI detection via environment variable:
The guards rely on the CI environment variable:
- Considered CI when
ENV["CI"]is set to any non-empty value other than"false"or"0"(case-insensitive). - Considered non-CI when
ENV["CI"]is unset/empty,"false", or"0".
Examples:
# Run a single file as if on CI
CI=true bundle exec rspec spec/path/to/file_spec.rb
# Also treated as CI
CI=1 bundle exec rspec
# Explicitly run as non-CI
CI=0 bundle exec rspecπ’ DB Dump on Failure
Dump database state to CSV files when an example fails. Useful to inspect exactly what data led to the failure.
- Defaults:
- Dumps all non-empty tables, excluding
schema_migrationsandar_internal_metadata - Exports each table to a separate CSV, ordered by primary key (if present)
- Writes to
tmp/rspec_power/db_failures/<timestamp>_<spec-name>/ - Includes
metadata.jsonwith spec info
- Dumps all non-empty tables, excluding
Enable for an example or group:
RSpec.describe User, :with_dump_db_on_fail do
it "creates a user" do
# ...
end
endCustomize which tables to include/exclude and output directory:
RSpec.describe Report, with_dump_db_on_fail: {
tables: ["users", "accounts"],
except: ["accounts"],
dir: Rails.root.join("tmp", "db_dumps").to_s
} do
it "fails and dumps only selected tables" do
# ...
end
endOptions:
-
tables/only: whitelist tables to dump -
except/exclude: tables to skip -
dir: base output directory (default:tmp/rspec_power/db_failures)
Compatibility: the legacy tag :dump_db_on_fail remains supported as an alias.
π― Shared Contexts
The gem provides several pre-configured shared contexts:
-
rspec_power::logging:verbose- Enables verbose logging for tests with:with_logmetadata -
rspec_power::logging:active_record- Enables ActiveRecord logging for tests with:with_log_armetadata -
rspec_power::env:override- Automatically handles environment variable overrides -
rspec_power::i18n:dynamic- Manages locale changes for tests with:with_localemetadata -
rspec_power::time:freeze- Handles time freezing for tests with:with_time_freezemetadata -
rspec_power::time:zone- Executes examples in a given time zone with:with_time_zonemetadata -
rspec_power::ci:only- Runs examples only in CI when tagged with:with_ci_only -
rspec_power::ci:skip- Skips examples in CI when tagged with:with_skip_ci -
rspec_power::request_dump:after- Dumps selected request state after each example with:with_request_dumpmetadata
π§ Configuration
The gem automatically configures itself, but you can customize the behavior:
# In spec_helper.rb or rails_helper.rb
RSpec.configure do |config|
# Customize logging behavior
config.include RSpecPower::Rails::LoggingHelpers
config.include_context "rspec_power::logging:verbose", with_log: true
config.include_context "rspec_power::logging:verbose", with_logs: true
config.include_context "rspec_power::logging:active_record", with_log_ar: true
# Customize environment helpers
config.include RSpecPower::Rails::EnvHelpers
config.include_context "rspec_power::env:override", :with_env
# Customize I18n helpers
config.include RSpecPower::Rails::I18nHelpers
config.include_context "rspec_power::i18n:dynamic", :with_locale
# Customize time helpers
config.include RSpecPower::Rails::TimeHelpers
config.include_context "rspec_power::time:freeze", :with_time_freeze
config.include_context "rspec_power::time:zone", :with_time_zone
# CI-only guards
config.include_context "rspec_power::ci:only", :with_ci_only
config.include_context "rspec_power::ci:skip", :with_skip_ci
# Request dump helpers (session/cookies/flash/headers)
config.include RSpecPower::RequestDumpHelpers
config.include_context "rspec_power::request_dump:after", :with_request_dump
endπ§ͺ Testing
Run the test suite:
bundle exec rspecLinter
bundle exec rubocopTo fix most issues, run:
bundle exec rubocop -Aπ€ Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
π Credits
Code for logging was extracted from test-prof gem.
π License
This project is licensed under the MIT License - see the MIT-LICENSE file for details.
Made with β€οΈ for the Rails testing community!

