Skip to content

Ror Testing

DodaTech 4 min read

title: Ruby on Rails Testing — Complete Guide to RSpec & Minitest description: 'Learn Ruby on Rails testing: RSpec setup, model specs, controller specs, request specs, factories, feature specs with Capybara, and test-driven development.' date: 2026-06-28 lastmod: 2026-06-28 weight: 26 tags: [backend, ror]


Rails testing uses RSpec or Minitest to verify application behavior through model specs, request specs, system specs, and integration tests with FactoryBot and Capybara.

## What You'll Learn

By the end of this tutorial, you'll set up RSpec, write model and request specs, use FactoryBot for test data, implement system specs with Capybara, and follow TDD practices.

## Real-World Use

A Rails app has 1000+ RSpec tests running in CI. Every pull request runs the full suite in 3 minutes. A red build blocks merging. Tests cover models, requests, and critical user flows.

## Testing Learning Path

```mermaid
flowchart LR
  A[API] --> B[Testing]
  B --> C[Asset Pipeline]
  C --> D[Mailers]
  D --> E[Background Jobs]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

RSpec Setup

# Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
  gem "faker"
end
group :test do
  gem "capybara"
  gem "selenium-webdriver"
  gem "shoulda-matchers"
end
bundle install
rails generate rspec:install
# Creates spec/spec_helper.rb and spec/rails_helper.rb

Model Specs

# spec/models/post_spec.rb
require "rails_helper"

RSpec.describe Post, type: :model do
  describe "validations" do
    it { should validate_presence_of(:title) }
    it { should validate_length_of(:title).is_at_least(5) }
    it { should validate_presence_of(:body) }
  end

  describe "associations" do
    it { should belong_to(:user) }
    it { should have_many(:comments).dependent(:destroy) }
  end

  describe "scopes" do
    let!(:published_post) { create(:post, published: true) }
    let!(:draft_post) { create(:post, published: false) }

    it "returns only published posts" do
      expect(Post.published).to include(published_post)
      expect(Post.published).not_to include(draft_post)
    end
  end
end

FactoryBot

# spec/factories/posts.rb
FactoryBot.define do
  factory :post do
    title { Faker::Book.title }
    body { Faker::Lorem.paragraphs(number: 3).join("\n\n") }
    published { true }
    user
  end
end

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    email { Faker::Internet.email }
    password { "password123" }
    username { Faker::Internet.username }
  end
end

Request Specs

# spec/requests/posts_spec.rb
require "rails_helper"

RSpec.describe "Posts", type: :request do
  let(:user) { create(:user) }
  let(:post_record) { create(:post, user: user) }

  describe "GET /posts" do
    it "returns a successful response" do
      get posts_path
      expect(response).to have_http_status(:success)
    end
  end

  describe "POST /posts" do
    context "when authenticated" do
      before { sign_in user }

      it "creates a new post" do
        expect {
          post posts_path, params: { post: attributes_for(:post) }
        }.to change(Post, :count).by(1)
        expect(response).to redirect_to(post_path(Post.last))
      end
    end

    context "when not authenticated" do
      it "redirects to login" do
        post posts_path, params: { post: attributes_for(:post) }
        expect(response).to redirect_to(new_user_session_path)
      end
    end
  end

  describe "DELETE /posts/:id" do
    it "destroys the post" do
      post_record  # Create the record
      expect {
        delete post_path(post_record)
      }.to change(Post, :count).by(-1)
    end
  end
end

System Specs (Capybara)

# spec/system/user_registration_spec.rb
require "rails_helper"

RSpec.describe "User Registration", type: :system do
  it "allows a user to sign up" do
    visit new_user_registration_path
    fill_in "Email", with: "test@example.com"
    fill_in "Password", with: "password123"
    fill_in "Password confirmation", with: "password123"
    click_button "Sign up"
    expect(page).to have_content("Welcome! You have signed up successfully.")
  end
end

Common Mistakes

1. Testing Implementation Details

Test behavior (what it does), not implementation (how it does it). Avoid testing private methods.

2. Slow Tests

Slow tests discourage running. Use let! over before(:each) carefully. Avoid unnecessary database hits.

3. Not Using Factories

Fixtures are hard to maintain. FactoryBot creates test data flexibly with traits and associations.

4. Skipping Request Specs

Request specs test the full HTTP stack (routing, auth, controllers). More valuable than controller specs.

5. Not Running Tests in CI

CI should run the full test suite on every push. A failing test should block deployment.

Practice Questions

1. What is RSpec?

A behavior-driven development (BDD) testing framework for Ruby. The most popular Rails test framework.

2. What is FactoryBot?

A gem for defining test data factories. Creates model instances with default attributes and associations.

3. What is the difference between model and request specs?

Model specs test model logic (validations, scopes). Request specs test full HTTP endpoints (controllers, auth, responses).

4. What are Capybara system specs?

System specs test user interactions in a browser (clicking, filling forms, navigating pages).

5. Challenge: Write a complete test suite for a Post model with validations, associations, and request specs.

# Model spec
RSpec.describe Post, type: :model do
  it { should validate_presence_of(:title) }
  it { should belong_to(:user) }
end
# Request spec
RSpec.describe "Posts", type: :request do
  it "lists posts" do
    get posts_path
    expect(response).to have_http_status(:ok)
  end
end

FAQ

Should I use RSpec or Minitest?

RSpec has better DSL and community support. Minitest is simpler and faster. Both are valid choices.

What is shoulda-matchers?

A gem that provides one-liner matchers for common Rails tests (validate_presence_of, belong_to).

How do I test file uploads?

Use fixture_file_upload for test files. Attach in factories with after(:build) or trait.

What is let vs let!?

let lazily evaluates (when first called). let! eagerly evaluates (before each test). Use let! for setup.

How do I speed up test suite?

Use parallel tests, avoid unnecessary DB hits, use Spring for preloading, and mock external services.

Mini Project: Test Suite

Write model, request, and system specs for a Rails blog app.

rails generate rspec:install
# spec/models/post_spec.rb - Write model specs
# spec/requests/posts_spec.rb - Write request specs
# spec/system/posts_spec.rb - Write system specs
bundle exec rspec

What's Next

Ruby on Rails Asset Pipeline Ruby on Rails Mailers

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro