inertia-rails-testing
Inertia Rails Testing
Testing patterns for Inertia responses with RSpec and Minitest.
For each controller action, verify:
- Correct component →
render_component('users/index') - Expected props →
have_props(users: satisfy { ... }) - No leaked data →
have_no_prop(:secret) - Flash messages →
follow_redirect!thenhave_flash(notice: '...') - Deferred props →
have_deferred_props(:analytics)
Common mistake: Forgetting follow_redirect! after PRG — without it, you're
asserting against the 302 redirect response, not the Inertia page that follows.
Setup
# spec/rails_helper.rb
require 'inertia_rails/rspec'
RSpec Matchers
| Matcher | Purpose |
|---|---|
be_inertia_response |
Verify response is Inertia format |
render_component('path') |
Check rendered component name |
have_props(key: value) |
Partial props match |
have_exact_props(key: value) |
Exact props match |
have_no_prop(:key) |
Assert prop absent |
have_flash(key: value) |
Partial flash match |
have_exact_flash(key: value) |
Exact flash match |
have_no_flash(:key) |
Assert flash absent |
have_deferred_props(:key) |
Check deferred props exist |
have_view_data(key: value) |
Partial view_data match |
RSpec Examples
ALWAYS use matchers (render_component, have_props, have_flash) instead of
direct property access (inertia.component, inertia.props[:key]):
# BAD — direct property access:
# expect(inertia.component).to eq('users/index')
# expect(inertia.props[:users].length).to eq(3)
# GOOD — use matchers:
expect(inertia).to render_component('users/index')
expect(inertia).to have_props(users: satisfy { |u| u.length == 3 })
# Key pattern: follow_redirect! after POST/PATCH/DELETE before asserting
it 'redirects with flash on success' do
post users_path, params: { user: valid_params }
expect(response).to redirect_to(users_path)
follow_redirect!
expect(inertia).to have_flash(notice: 'User created!')
end
it 'returns validation errors on failure' do
post users_path, params: { user: { name: '' } }
follow_redirect!
expect(inertia).to have_props(errors: hash_including('name' => anything))
end
Test Shared Props
Shared props from inertia_share are included in inertia.props. The inertia
helper is available in type: :request specs after requiring inertia_rails/rspec:
it 'includes shared auth data' do
sign_in(user)
get dashboard_path
expect(inertia).to have_props(
auth: hash_including(user: hash_including(id: user.id))
)
end
it 'excludes auth data for unauthenticated users' do
get dashboard_path
expect(inertia).to have_props(auth: hash_including(user: nil))
end
Test Deferred Props
it 'defers expensive analytics data' do
get dashboard_path
expect(inertia).to have_deferred_props(:analytics, :statistics)
expect(inertia).to have_deferred_props(:slow_data, group: :slow)
end
Partial Reload Helpers
it 'supports partial reload' do
get users_path
# Simulate partial reload — only fetch specific props
inertia_reload_only(:users, :pagination)
# Or exclude specific props
inertia_reload_except(:expensive_stats)
# Load deferred props
inertia_load_deferred_props(:default)
inertia_load_deferred_props # loads all groups
end
Test External Redirects (inertia_location)
it 'redirects to Stripe via inertia_location' do
post checkout_path
# inertia_location returns 409 with X-Inertia-Location header
expect(response).to have_http_status(:conflict)
expect(response.headers['X-Inertia-Location']).to match(/stripe\.com/)
end
NEVER Test These
- Inertia framework behavior — don't test that
<Form>sends CSRF tokens or thatrouter.visitworks. Test YOUR controller logic. - Exact prop structure — use
have_props(users: satisfy { ... }), not deep equality on full JSON. Brittle tests break when you add a column. - Flash without
follow_redirect!— after POST/PATCH/DELETE with redirect, you MUSTfollow_redirect!before asserting flash or props. Without it you're asserting against the 302, not the page. - Deferred prop values on initial load — deferred props are
nilin the initial response because they're fetched in a separate request after page render. Usehave_deferred_props(:key)to verify they're registered, orinertia_load_deferred_propsto resolve them in tests. - Mocked Inertia responses — use
type: :requestspecs that exercise the full stack.type: :controllerspecs withassignsdon't work because Inertia uses a custom render pipeline that only executes in the full request cycle.
Direct Access (use matchers above when possible)
inertia.component # => 'users/index'
inertia.props # => { users: [...] }
inertia.props[:users] # direct prop access
inertia.flash # => { notice: 'Created!' }
inertia.deferred_props # => { default: [:analytics], slow: [:report] }
Related Skills
- Controller patterns →
inertia-rails-controllers(prop types, flash, PRG) - Form flows →
inertia-rails-forms(submission, validation) - Deferred/shared props →
inertia-rails-pages(Deferred, usePage)
References
MANDATORY — READ ENTIRE FILE when writing Minitest tests (not RSpec):
references/minitest.md (~40 lines) — Minitest assertions
equivalent to the RSpec matchers above.
Do NOT load minitest.md for RSpec projects — the matchers above are all
you need.