rspec-testing

RSpec Testing for Rails

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rspec-testing" with this command: npx skills add dgalarza/claude-code-workflows/dgalarza-claude-code-workflows-rspec-testing

RSpec Testing for Rails

Overview

Write comprehensive, maintainable RSpec tests following industry best practices. This skill combines guidance from Better Specs and thoughtbot's testing guides to produce high-quality test coverage for Rails applications.

Core Testing Principles

  1. Test-Driven Development (TDD)

Follow the Red-Green-Refactor cycle:

  • Red: Write failing tests that define expected behavior

  • Green: Implement minimal code to make tests pass

  • Refactor: Improve code while tests continue to pass

  1. Test Structure (Arrange-Act-Assert)

Organize tests with clear phases separated by newlines:

it 'creates a new article' do

Arrange - set up test data

user = create(:user) attributes = {title: 'Test Article', body: 'Content here'}

Act - perform the action

article = Article.create(attributes)

Assert - verify the outcome

expect(article).to be_persisted expect(article.title).to eq('Test Article') end

  1. Single Responsibility

Each test should verify one behavior. For unit tests, use one expectation per test. For integration tests, multiple expectations are acceptable when testing a complete flow.

  1. Test Real Behavior

Avoid over-mocking. Test actual application behavior when possible. Only stub external services, slow operations, and dependencies outside your control.

Test Type Decision Tree

When to Write Model Specs

Use model specs (spec/models/ ) for:

  • Validations

  • Associations

  • Scopes

  • Instance methods

  • Class methods

  • Enums and constants

  • Database constraints

Example:

spec/models/article_spec.rb

RSpec.describe Article do describe 'validations' do it 'validates presence of title' do article = build(:article, title: nil) expect(article).not_to be_valid expect(article.errors[:title]).to include("can't be blank") end end

describe 'associations' do it { is_expected.to belong_to(:user) } it { is_expected.to have_many(:comments) } end

describe '#published?' do it 'returns true when status is published' do article = build(:article, status: :published) expect(article.published?).to be true end end end

When to Write Controller Specs

Use controller specs (spec/controllers/ ) for:

  • Authorization checks (Pundit/CanCanCan)

  • Request routing and parameter handling

  • Response status codes

  • Instance variable assignments

  • Flash messages

  • Redirects

Example:

spec/controllers/articles_controller_spec.rb

RSpec.describe ArticlesController do describe 'POST #create' do context 'with valid parameters' do it 'creates a new article and redirects' do user = create(:user) session[:user_id] = user.id

    valid_attributes = {
      title: 'Test Article',
      body: 'Article content'
    }

    expect do
      post :create, params: {article: valid_attributes}
    end.to change(Article, :count).by(1)

    expect(response).to redirect_to(Article.last)
  end
end

context 'with invalid parameters' do
  it 'does not create article and renders new template' do
    user = create(:user)
    session[:user_id] = user.id

    invalid_attributes = {title: '', body: ''}

    expect do
      post :create, params: {article: invalid_attributes}
    end.not_to change(Article, :count)

    expect(response).to render_template(:new)
  end
end

end end

When to Write System Specs

Use system specs (spec/system/ ) for:

  • End-to-end user workflows

  • Multi-step interactions

  • JavaScript functionality

  • Form submissions

  • Navigation flows

  • Real user scenarios

Naming convention: user_action_spec.rb or feature_description_spec.rb

Example:

spec/system/article_creation_spec.rb

RSpec.describe 'Article Creation' do it 'allows a user to create a new article' do user = create(:user)

# Sign in
visit '/login'
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password'
click_button 'Sign In'

# Navigate to new article page
click_link 'New Article'
expect(page).to have_current_path(new_article_path)

# Fill out the article form
fill_in 'Title', with: 'My Test Article'
fill_in 'Body', with: 'This is the article content'
select 'Published', from: 'Status'

# Submit the form
click_button 'Create Article'

expect(page).to have_content('Article created successfully!')
expect(page).to have_content('My Test Article')

end end

When to Write Component Specs

Use component specs (spec/components/ ) for:

  • ViewComponent rendering

  • Variant behavior

  • Slot functionality

  • Conditional rendering

  • Component attributes

Example:

spec/components/button_component_spec.rb

RSpec.describe ButtonComponent, type: :component do describe 'variants' do it 'renders primary variant' do render_inline(described_class.new(variant: :primary)) { 'Click me' }

  button = page.find('button')
  expect(button[:class]).to include('btn-primary')
  expect(page).to have_button('Click me')
end

it 'renders secondary variant' do
  render_inline(described_class.new(variant: :secondary)) { 'Cancel' }

  button = page.find('button')
  expect(button[:class]).to include('btn-secondary')
end

end end

When to Write Service/Integration Specs

Use service/integration specs (spec/services/ , spec/integration/ ) for:

  • Complex business logic

  • Multi-step workflows

  • External API integrations

  • Background job processing

  • Data transformations

RSpec Syntax & Style Guide

Describe Blocks

Use Ruby documentation conventions:

  • .method_name for class methods

  • #method_name for instance methods

describe '.find_by_title' do # class method describe '#publish' do # instance method describe 'validations' do # grouping

Context Blocks

Start with "when," "with," or "without":

context 'when user is admin' do context 'with valid parameters' do context 'without authentication' do

It Blocks

  • Keep descriptions under 40 characters

  • Use third-person present tense

  • Never use "should" in descriptions

✅ Good

it 'creates a new article' do it 'validates presence of title' do it 'redirects to dashboard' do

❌ Bad

it 'should create a new article' do it 'should validate presence of title' do

Expectations

Always use expect syntax (never should ):

✅ Good

expect(article).to be_valid expect(response).to have_http_status(:success) expect { action }.to change(Article, :count).by(1)

❌ Bad (deprecated)

article.should be_valid response.should have_http_status(:success)

One-Liners

Use is_expected for concise one-line specs:

subject { article }

it { is_expected.to be_valid } it { is_expected.to be_persisted }

System Test Best Practices

Authentication in System Tests

Test authentication flows directly without stubbing:

Good - test the actual login flow

visit '/login' fill_in 'Email', with: user.email fill_in 'Password', with: 'password' click_button 'Sign In'

expect(page).to have_content('Dashboard')

Controller Test Authentication

For controller tests, use direct session assignment rather than stubbing:

✅ Good - direct session assignment

session[:user_id] = user.id

❌ Avoid - stubbing authentication

allow_any_instance_of(Controller).to receive(:logged_in?).and_return(true)

Avoid CSS Class Testing

Don't test implementation details like CSS utility classes. Test semantic selectors and content:

✅ Good - semantic selectors

expect(page).to have_selector(:test_id, 'user-modal') expect(page).to have_css("[aria-hidden='false']") expect(page).to have_content('Success message') expect(page).to have_button('Submit')

❌ Bad - coupling to CSS implementation

expect(page).to have_css('.opacity-100') expect(page).to have_css('.bg-red-500') expect(page).to have_css('.rounded-lg')

Factory Patterns

Organization

  • Associations (implicit) first

  • Attributes (alphabetical)

  • Traits (alphabetical)

FactoryBot.define do factory :article do # Associations user category

# Attributes (alphabetical)
body { 'Article content goes here...' }
published_at { Time.current }
status { :draft }
title { 'Sample Article Title' }

# Traits (alphabetical)
trait :published do
  status { :published }
  published_at { 1.day.ago }
end

trait :with_tags do
  after(:create) do |article|
    create_list(:tag, 3, article: article)
  end
end

end end

Prefer Build Over Create

Use build and build_stubbed when database persistence isn't needed:

✅ Good - fast, no database hit

it 'validates title format' do article = build(:article, title: '') expect(article).not_to be_valid end

Less optimal - unnecessary database hit

it 'validates title format' do article = create(:article, title: '') expect(article).not_to be_valid end

Common Testing Patterns

Testing Validations

describe 'validations' do it 'validates presence of title' do article = build(:article, title: nil) expect(article).not_to be_valid expect(article.errors[:title]).to include("can't be blank") end

it 'validates length of title' do article = build(:article, title: 'a' * 256) expect(article).not_to be_valid end

it 'allows valid titles' do article = build(:article, title: 'Valid Title') expect(article).to be_valid end end

Testing Enums

describe 'enums' do it 'defines status enum' do expect(described_class.statuses).to eq({ 'draft' => 'draft', 'published' => 'published', 'archived' => 'archived' }) end

it 'has correct default' do article = described_class.new expect(article.status).to eq('draft') end end

Testing Authorization

context 'when user is not admin' do it 'raises authorization error' do user = create(:user, role: :member) session[:user_id] = user.id

expect do
  get :admin_dashboard
end.to raise_error(Pundit::NotAuthorizedError)

end end

Using Shoulda Matchers

describe 'associations' do it { is_expected.to belong_to(:user) } it { is_expected.to have_many(:comments) } end

describe 'validations' do it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_length_of(:title).is_at_most(255) } end

What to Avoid

❌ Don't Stub the System Under Test

Never mock or stub methods on the class being tested:

❌ Bad

it 'processes payment' do order = Order.new allow(order).to receive(:calculate_total).and_return(100) expect(order.process_payment).to be true end

✅ Good

it 'processes payment' do order = Order.new(line_items: [line_item]) expect(order.process_payment).to be true end

❌ Don't Test Private Methods

Test the public interface. Private methods are tested indirectly:

❌ Bad

describe '#calculate_total (private)' do it 'sums line items' do order.send(:calculate_total) end end

✅ Good

describe '#total' do it 'returns sum of line items' do expect(order.total).to eq(100) end end

❌ Avoid any_instance_of

Use dependency injection instead:

❌ Bad

allow_any_instance_of(PaymentService).to receive(:charge)

✅ Good

payment_service = instance_double(PaymentService) allow(payment_service).to receive(:charge).and_return(success) order = Order.new(payment_service: payment_service)

Quick Reference

Test Organization

RSpec.describe ClassName do

Setup (let, before)

let(:resource) { create(:resource) }

before do # common setup end

Validations

describe 'validations' do end

Associations

describe 'associations' do end

Class methods

describe '.class_method' do end

Instance methods

describe '#instance_method' do context 'when condition' do it 'does something' do end end end end

Expectation Matchers

Equality

expect(value).to eq(expected) expect(value).to be(expected) # same object expect(value).to match(/regex/)

Predicates

expect(object).to be_valid expect(object).to be_persisted expect(collection).to be_empty

Collections

expect(array).to include(item) expect(array).to contain_exactly(1, 2, 3) expect(hash).to have_key(:name)

Changes

expect { action }.to change(Model, :count).by(1) expect { action }.to change { object.attribute }.from(old).to(new)

Errors

expect { action }.to raise_error(ErrorClass) expect { action }.not_to raise_error

Resources

This skill includes detailed reference documentation in the references/ directory:

references/better_specs_guide.md

Comprehensive patterns from Better Specs including:

  • Describe/context/it block conventions

  • Subject and let usage

  • Mocking strategies

  • Shared examples

  • Factory patterns

references/thoughtbot_patterns.md

thoughtbot's RSpec best practices covering:

  • Modern RSpec syntax

  • Test structure and organization

  • What to avoid in tests

  • Capybara patterns for system tests

  • Factory organization

Load these references when you need detailed examples or are unsure about a specific pattern.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

tdd-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

process-meeting-transcript

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

linear-implement

No summary provided by upstream source.

Repository SourceNeeds Review