skills/testing-patterns/SKILL.md
Write automated tests using RSpec, Capybara, and FactoryBot for Rails applications. Use when implementing features, fixing bugs, or when the user mentions testing, specs, RSpec, Capybara, or test data. Avoid using rails console or server for testing.
npx skillsauth add rolemodel/rolemodel-skills testing-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Write automated tests using RSpec and Capybara. Avoid using the Rails console or starting a Rails server for testing.
bundle exec rspec
let over instance variablesUse let (lazy evaluation) when:
Use let! (eager evaluation) when:
Example - System Tests:
RSpec.describe 'Job Management', type: :system do
# Use let! - these records must exist in DB for dropdowns and queries
let!(:location) { create(:location, name: 'Downtown Site') }
let!(:superintendent) { create(:user, :superintendent) }
# Use let - only created when explicitly referenced in a test
let(:job) { create(:job, name: 'Test Job', location:, superintendent:) }
it 'shows location in dropdown' do
visit new_job_path
# location must exist in DB for dropdown to display it
expect(page).to have_select('Location', with_options: [location.name])
end
it 'can delete a job' do
visit job_path(job) # job created here when first referenced
click_button 'Delete'
end
end
Common Pitfall:
# ❌ WRONG - Test will fail because job2 doesn't exist in DB yet
let(:job1) { create(:job, name: 'Job 1') }
let(:job2) { create(:job, name: 'Job 2') }
it 'lists all jobs' do
visit jobs_path
expect(page).to have_content('Job 1') # job1 created when referenced
expect(page).to have_content('Job 2') # FAIL - job2 never referenced, not in DB
end
# ✅ CORRECT - Both jobs exist before test runs
let!(:job1) { create(:job, name: 'Job 1') }
let!(:job2) { create(:job, name: 'Job 2') }
it 'lists all jobs' do
visit jobs_path
expect(page).to have_content('Job 1') # Both jobs already in DB
expect(page).to have_content('Job 2') # Test passes
end
Key Insight:
If you expect to see data without explicitly interacting with the object variable (like viewing a list or selecting from a dropdown), use let! to ensure the record exists in the database.
Test validations explicitly using build with invalid data, then verify the model is invalid and check error messages:
describe 'validations' do
it 'must have a start date' do
membership = build(:membership, start_date: nil)
expect(membership).not_to be_valid
expect(membership.errors.full_messages).to contain_exactly "Start date can't be blank"
end
it 'enforces end date must follow start date' do
membership = build(:membership, start_date: 1.year.ago, end_date: 2.years.ago)
expect(membership).not_to be_valid
expect(membership.errors.full_messages).to contain_exactly 'End date must follow start date'
end
it 'permits empty end date' do
membership = build(:membership, start_date: 1.year.ago, end_date: nil)
expect(membership).to be_valid
end
end
Key points:
build instead of create to avoid database writesfull_messagesdata-testid attributes with dom_id for reliable element selection:js (e.g. it 'does something', :js do) for specs that run javascript such as stimulus controllersUse data-testid attributes with dom_id for stable, reliable element selection that's resistant to UI changes:
View:
tbody
- @entries.each do |entry|
tr data-testid=dom_id(entry)
td= entry.name
Spec:
within(data_test(entry1)) do
click_button 'Submit'
end
Benefits:
Avoid:
within('tr', text: 'Entry 1')When elements are ambiguous (multiple buttons/links with same text), use within blocks to scope interactions:
Best Practice:
Always use within blocks when:
When testing actions that trigger Turbo confirm dialogs (e.g., delete buttons with data: { turbo_confirm: 'message' }), use the provided helper methods.
Setup:
Create the helper file:
# spec/support/turbo_confirm_helper.rb
module TurboConfirmHelper
def accept_turbo_confirm
yield
expect(page).to have_css '.confirm-dialog-wrapper--active', wait: 5
sleep(0.5)
within '.confirm-dialog-wrapper--active' do
find('#confirm-accept').click
end
expect(page).to_not have_css '.confirm-dialog-wrapper--active', wait: 5
end
def deny_turbo_confirm
yield
expect(page).to have_css '.confirm-dialog-wrapper--active', wait: 5
sleep(0.5)
within '.confirm-dialog-wrapper--active' do
find('#confirm-cancel').click
end
expect(page).to_not have_css '.confirm-dialog-wrapper--active', wait: 5
end
end
Include in RSpec configuration:
# spec/support/helpers.rb
RSpec.configure do |c|
# ...existing code...
c.include TurboConfirmHelper, type: :system
end
Usage in Tests:
accept_turbo_confirm do
click_button 'Delete'
end
deny_turbo_confirm do
click_button 'Delete'
end
Key Points:
:js tag for tests involving Turbo confirm dialogsaccept_turbo_confirm to click "Yes, I'm Sure"deny_turbo_confirm to click "Cancel"build or create instead of direct model instantiationbuild for validation tests to avoid database writescreate when you need persisted recordstesting
Verify what Ruby versions actually exist and install a specific Ruby via rbenv. Use BEFORE asserting that any Ruby version does or doesn't exist (e.g., "Ruby 4.0 isn't out yet", "the latest Ruby is 3.x", "Ruby X.Y.Z doesn't exist"). Also use when the user asks "what's the latest Ruby", "is Ruby X out", "does Ruby X.Y exist", "install Ruby", "switch to Ruby X", "what Ruby is installed", or mentions a specific Ruby version you're unsure about. Claude's training data may be out of date — run `check.sh` first.
development
Trace code through the stack — upward to entry points, downward to data, or laterally across boundaries. Use when the user asks "where does this get called from", "what calls this method", "trace this through the stack", "how does this request flow", "where does this data come from", "follow this through the code", or pastes/selects a piece of code and wants to understand where it fits in the larger system.
tools
Pick the single highest-priority unresolved Sentry issue and hand it off to a fixer skill. Use when triaging Sentry errors, running automated issue triage, or when asked to fix the top Sentry issue in a project.
tools
Find and fix issues from Sentry using MCP. Use when asked to fix Sentry errors, debug production issues, investigate exceptions, or resolve bugs reported in Sentry. Methodically analyzes stack traces, breadcrumbs, traces, and context to identify root causes.