Crspec
Crspec is a next-generation concurrent, fiber-isolated Ruby testing framework ecosystem. By leveraging Ruby 3.2+ Fiber Storage (Fiber[:key]), fiber-aware method interceptors, non-blocking fiber schedulers, and ruby/prism AST transpilation, Crspec achieves ultra-fast concurrent test execution while maintaining full syntax compatibility with traditional RSpec suites.
Key Features
- Fiber Storage Context Isolation: Solves legacy thread-local (
Thread.current) state leakage by routing all execution metadata andletmemoization through inherited Fiber storage (Fiber[:crspec_execution_context]). - Fiber-Aware Mocking (
crspec-mock): Prevents mock registry corruption across parallel tests using prepended method interceptors bound toFiber[:crspec_mock_space]. - Rails Integration & ActiveSupport Reuse (
crspec-rails): Direct integration withActiveSupport::Testing::Assertions,ActiveSupport::Testing::TimeHelpers, andActiveSupport::Testing::FileFixtures. Leases database connections per example fiber (ActiveRecord::Base.lease_connection), handles savepoint transaction strategies (SAVEPOINT ex_root), and integrates with Rails parallel worker suites (Crspec::Rails::Parallel). - Rails Request Specs: Expressive HTTP request verb helpers (
get,post,put,patch,delete), response status inspection (response.status), headers, andjson_responseparsing. - Prism-Based AST Transpiler (
crspec-transpiler): Automated migration tool usingruby/prismC-parser to convert existing RSpec codebases to Crspec syntax and flag thread-unsafe constructs likebefore(:all). - Concurrent Execution Kernel: Multi-threaded worker pool integrated with real-time progress dot formatting and non-blocking fiber schedulers (
Async::Scheduler).
Installation
Add crspec to your application's Gemfile:
gem "crspec", github: "crspec/crspec"
Then install via Bundler (or mise):
mise exec -- bundle install
Spec Helpers & Generator
Initialize standard spec_helper.rb (and rails_helper.rb if run within a Rails project):
mise exec -- crspec --init
Spec files include helpers explicitly via standard require statements:
require "spec_helper"
# or
require "rails_helper"
Writing Specs
crspec provides a familiar RSpec-style DSL for structuring example groups and expectations:
require "spec_helper"
Crspec.describe User, type: :model do
let(:valid_attributes) { { name: "Jane Doe", email: "jane@example.com" } }
subject(:user) { User.new(valid_attributes) }
it "validates primary attributes" do
expect(user.valid?).to be(true)
end
it "changes user count when created" do
expect {
User.create!(valid_attributes)
}.to change(User, :count).by(1)
end
context "when email is omitted" do
let(:valid_attributes) { { name: "Jane Doe", email: nil } }
it "flags validation errors" do
expect(user.valid?).to be(false)
expect(user.errors[:email]).to include("can't be blank")
end
end
end
Supported Matchers
eq(expected)be(expected)/be_nil/be_truthy/be_falsyinclude(*items)raise_error(ExceptionClass, "message")respond_to(*methods)change(receiver, :method).by(amount)/change { ... }.by(amount)/.from(val).to(val)
Advanced: Execution Context & Extensions
Regular spec authors do not need to interact with execution_context at all—crspec automatically isolates let memoization, database connections, and stubs invisibly per test.
For gem developers, middleware, or custom test extension authors needing request tracing across child fibers, execution_context (or current_context) provides an encapsulated, thread-safe store:
before do
execution_context[:trace_id] = "TX-12345"
end
it "reads execution context metadata" do
expect(execution_context[:trace_id]).to eq("TX-12345")
end
Fiber-Isolated Mocking (crspec-mock)
crspec-mock routes all doubles and stubs through Fiber[:crspec_mock_space], allowing parallel tests to stub methods on shared target objects without cross-test pollution:
Crspec.describe PaymentGateway do
let(:stripe_client) { instance_double(Stripe::Charge) }
it "stubs credit card processing concurrently" do
allow(stripe_client).to receive(:create).with(amount: 5000).and_return(status: "succeeded")
result = stripe_client.create(amount: 5000)
expect(result[:status]).to eq("succeeded")
end
end
Rails Concurrency & Request Specs (crspec-rails)
Database Isolation & Connection Leasing
rails_helper.rb automatically configures database-independent transaction isolation using your application's config/database.yml:
Crspec.configure do |config|
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.include(Crspec::Rails::RequestHelpers)
config.around(:each) do |example|
if defined?(ActiveRecord::Base) && config.use_transactional_fixtures
Crspec::Rails::DatabaseIsolation.wrap_example(example)
else
example.execute!
end
end
end
Request Specs
Perform full REST API controller testing with built-in request helpers:
Crspec.describe "Books API", type: :request do
it "creates a book via POST" do
post "/books", params: { name: "Refactoring", author: "Martin Fowler" }
expect(response.status).to eq(201)
expect(json_response[:name]).to eq("Refactoring")
end
end
Rails Parallel Testing Setup
Configure worker counts and setup/teardown hooks:
Crspec::Rails::Parallel.parallelize(workers: 4) do
parallelize_setup do |worker_number|
puts "Worker #{worker_number} starting"
end
parallelize_teardown do |worker_number|
puts "Worker #{worker_number} tearing down"
end
end
Migrating from RSpec (crspec-transpile)
Use the built-in Prism-powered transpiler CLI to convert legacy RSpec suites to Crspec:
# 1. Analyze your test directory for thread-unsafe patterns (e.g. before(:all))
mise exec -- crspec-transpile --analyze spec/
# 2. Automatically transpile RSpec code to Crspec syntax in-place
mise exec -- crspec-transpile --write spec/
Sample Rails Bookstore App
Explore the working sample Rails application in samples/book_store:
mise exec -- ./exe/crspec samples/book_store/spec/
Running Specs
Execute your test suite concurrently using the crspec executable:
# Run specs with default concurrency (number of CPU cores)
mise exec -- crspec spec/
# Run specs with specific concurrency
mise exec -- crspec -c 8 spec/models/
Development & Testing
To run the internal framework unit tests (built with Minitest):
mise exec -- bundle exec rake test
License
The gem is available as open source under the terms of the MIT License.