RoR Integration Tests

Integration tests simulate real user interactions with your application. Unlike model tests that check isolated logic, integration tests drive through multiple layers — routes, controllers, views, and the database — in a single test. They verify that different parts of your app work correctly together and that complete user workflows succeed end to end.

Integration Tests vs Unit Tests

Unit Test (model spec):
  Creates a User object, calls user.valid?, checks the result
  Fast. Tests one thing. No browser. No HTTP.

Integration / System Test:
  Visits /users/new in a simulated browser
  Fills in the form
  Clicks submit
  Checks that "Account created!" appears on screen
  Checks the database for the new record
  Slower. Tests the full user journey.

System Specs with Capybara

Rails system tests use Capybara, which drives a real browser or headless browser to simulate user actions:

Gemfile:
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"    ← auto-downloads browser drivers
spec/system/user_registration_spec.rb

require "rails_helper"

RSpec.describe "User Registration", type: :system do
  it "allows a new user to sign up" do
    visit new_user_registration_path

    fill_in "Name",                 with: "Alice Smith"
    fill_in "Email",                with: "alice@example.com"
    fill_in "Password",             with: "password123"
    fill_in "Password confirmation",with: "password123"
    click_button "Create Account"

    expect(page).to have_content("Welcome! Your account is ready.")
    expect(User.count).to eq(1)
    expect(User.last.email).to eq("alice@example.com")
  end

  it "shows errors for invalid input" do
    visit new_user_registration_path

    fill_in "Email", with: "not-an-email"
    click_button "Create Account"

    expect(page).to have_content("Email is invalid")
    expect(User.count).to eq(0)
  end
end

Capybara Actions Reference

Navigation:
  visit "/articles"
  visit article_path(@article)
  click_link "Read More"
  click_button "Submit"

Form Interaction:
  fill_in "Email", with: "user@example.com"
  fill_in "Name",  with: "Alice"
  select "Editor", from: "Role"
  check "Accept terms"
  uncheck "Newsletter"
  attach_file "Avatar", Rails.root.join("spec/fixtures/avatar.jpg")

Assertions:
  expect(page).to have_content("Welcome back!")
  expect(page).to have_text("3 articles")
  expect(page).to have_link("Edit")
  expect(page).to have_button("Save")
  expect(page).to have_css(".alert-success")
  expect(page).not_to have_content("Error")
  expect(current_path).to eq(articles_path)

A Complete User Workflow Test

spec/system/article_management_spec.rb

RSpec.describe "Article Management", type: :system do
  let(:user) { create(:user) }

  before { sign_in user }   ← Devise test helper

  it "allows a user to create and then edit an article" do
    # CREATE
    visit new_article_path

    fill_in "Title", with: "My First Article"
    fill_in "Body",  with: "This is the content of my article."
    click_button "Publish Article"

    expect(page).to have_content("Article created!")
    expect(page).to have_content("My First Article")

    # EDIT
    click_link "Edit"

    fill_in "Title", with: "My Updated Article"
    click_button "Save Changes"

    expect(page).to have_content("Article updated!")
    expect(page).to have_content("My Updated Article")
    expect(page).not_to have_content("My First Article")
  end

  it "allows a user to delete an article" do
    article = create(:article, user: user, title: "To Delete")

    visit articles_path
    expect(page).to have_content("To Delete")

    click_link "To Delete"
    click_button "Delete"

    expect(page).to have_content("Article deleted.")
    expect(page).not_to have_content("To Delete")
    expect(Article.count).to eq(0)
  end
end

Headless Browser — Faster System Tests

By default, system tests open a real Chrome window. Switch to headless mode for faster tests in CI environments:

spec/support/capybara.rb

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium_chrome_headless
  end
end

Testing Pagination

it "paginates articles and shows 10 per page" do
  create_list(:article, 15, published: true)

  visit articles_path
  expect(page).to have_css(".article-card", count: 10)

  click_link "Next"
  expect(page).to have_css(".article-card", count: 5)
end

Testing File Uploads

it "allows a user to upload a profile picture" do
  sign_in create(:user)
  visit edit_user_path(user)

  attach_file "Avatar", Rails.root.join("spec/fixtures/files/avatar.jpg")
  click_button "Save Profile"

  expect(page).to have_content("Profile updated successfully.")
  expect(user.reload.avatar).to be_attached
end

Waiting for Dynamic Content

Capybara automatically waits for elements to appear (default 2 seconds). For slower operations like AJAX requests, increase the wait time:

using_wait_time(10) do
  expect(page).to have_content("Results loaded")
end

Test Folder Organization

spec/
  models/           ← unit tests (fast)
  requests/         ← HTTP request/response tests (medium)
  system/           ← browser-based integration tests (slow)
  factories/        ← FactoryBot definitions
  fixtures/
    files/          ← test files (images, PDFs)
  support/
    capybara.rb
    factory_bot.rb
    devise.rb

When Integration Tests Catch What Unit Tests Miss

Unit test passed:
  User model validates email ✓
  ArticlesController creates article ✓

Integration test fails:
  Visit /articles/new → fill form → submit → "500 Internal Server Error"
  Reason: a missing before_action was causing a NilClass error
           only visible when the full stack runs together

Integration tests provide confidence that your entire application works as a system. Run them before every deployment to catch bugs that only surface when multiple components interact.

Leave a Comment

Your email address will not be published. Required fields are marked *