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.
82
77%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./skills/testing-patterns/SKILL.mdWrite automated tests using RSpec and Capybara. Avoid using the Rails console or starting a Rails server for testing.
bundle exec rspeclet 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
endCommon 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
endKey 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
endKey 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.nameSpec:
within(data_test(entry1)) do
click_button 'Submit'
endBenefits:
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
endInclude in RSpec configuration:
# spec/support/helpers.rb
RSpec.configure do |c|
# ...existing code...
c.include TurboConfirmHelper, type: :system
endUsage in Tests:
accept_turbo_confirm do
click_button 'Delete'
end
deny_turbo_confirm do
click_button 'Delete'
endKey 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 records097ad6b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.