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
ruby# 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]):
ruby# 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 })
ruby# 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:
rubyit '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
rubyit 'excludes auth data for unauthenticated users' do get dashboard_path expect(inertia).to have_props(auth: hash_including(user: nil)) end
Test Deferred Props
rubyit '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
rubyit '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)
rubyit '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)
rubyinertia.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.

