Testing

Purpose

Testing is essential to maintaining a healthy Rails application.

Rails provides powerful testing tools, but their flexibility means teams must establish clear conventions to keep tests fast, reliable and maintainable.

This guide defines the testing strategy for Rails applications, building on the general testing principles in the Testing Strategies Guide.

Test Distribution

Follow the test pyramid for Rails applications:

        /\
       /  \
      /E2E \    Few: system specs, critical user journeys
     /------\
    / Request\  Some: controller behaviour, API responses
   /  specs   \
  /------------\
 / Model & Unit \ Many: models, services, forms, presenters
/     specs      \
------------------
TypeSpeedPurposeProportion
Model specsFastValidations, associations, scopes.40%
Unit specsFastServices, forms, presenters,30%
queries.
Request specsModerateController behaviour, status codes,20%
response format, authentication.
System specsSlowCritical user journeys through the10%
full stack.

Model Specs

Test validations, associations, scopes and custom methods.

RSpec.describe User, type: :model do
  describe "validations" do
    it { is_expected.to validate_presence_of(:email) }
    it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
  end

  describe "associations" do
    it { is_expected.to have_many(:posts).dependent(:destroy) }
  end

  describe "scopes" do
    describe ".active" do
      it "returns users active within the last 30 days" do
        active_user = create(:user, last_active_at: 1.day.ago)
        inactive_user = create(:user, last_active_at: 31.days.ago)

        expect(User.active).to contain_exactly(active_user)
      end
    end
  end
end

Request Specs

Test controller behaviour at the HTTP level.

Request specs replace controller specs in modern Rails. They test the entire request-response cycle without rendering views.

RSpec.describe "Users API", type: :request do
  describe "POST *api*v1*users" do
    let(:params) { { user: { email: "user@example.com", name: "Alice" } } }

    it "creates a user" do
      expect { post "*api/v1*users", params: params }
        .to change(User, :count).by(1)
    end

    it "returns 201" do
      post "*api/v1*users", params: params
      expect(response).to have_http_status(:created)
    end

    context "with invalid params" do
      let(:params) { { user: { email: "" } } }

      it "returns 422" do
        post "*api/v1/users", params: params
        expect(response).to have_http_status(:unprocessable_entity)
      end
    end
  end
end

System Specs

Test critical user journeys through the full stack, including JavaScript.

Use system specs sparingly. Cover only the most important business flows.

RSpec.describe "User Registration", type: :system do
  it "allows a new user to register" do
    visit root_path
    click_on "Sign Up"

    fill_in "Email", with: "user@example.com"
    fill_in "Password", with: "securepassword"
    click_on "Create Account"

    expect(page).to have_text("Welcome!")
  end
end

Factory Patterns

Use factories for test data. Keep factories simple and focused.

FactoryBot.define do
  factory :user do
    email { generate(:email) }
    password { "password123" }
    name { "Alice" }
  end
end

Factory Guidelines

What to Avoid

Controller Specs

Prefer request specs over controller specs. Request specs test the full HTTP layer and are more reliable.

View Specs

Prefer system specs or request specs over view specs. View specs are brittle and test implementation details.

Heavy Fixtures

Prefer factories over fixtures. Factories are more flexible and easier to maintain.

AI Integration

AI assistants can help with Rails testing by:

When asking AI for testing help, provide:

Related Documents