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 and let memoization 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 to Fiber[:crspec_mock_space].
  • Rails Integration & ActiveSupport Reuse (crspec-rails): Direct integration with ActiveSupport::Testing::Assertions, ActiveSupport::Testing::TimeHelpers, and ActiveSupport::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, and json_response parsing.
  • Prism-Based AST Transpiler (crspec-transpiler): Automated migration tool using ruby/prism C-parser to convert existing RSpec codebases to Crspec syntax and flag thread-unsafe constructs like before(: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_falsy
  • include(*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.