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.
