inertia-rails-testing
>-
Works with
---
name: inertia-rails-testing
description: >-
license: MIT
---
# 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!` then `have_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`:
```ruby
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
```
```ruby
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
```ruby
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
```ruby
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)
```ruby
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 that `router.visit` works. 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 MUST `follow_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 `nil` in the initial response because they're fetched in a separate request after page render. Use `have_deferred_props(:key)` to verify they're registered, or `inertia_load_deferred_props` to resolve them in tests.
- **Mocked Inertia responses** — use `type: :request` specs that exercise the full stack. `type: :controller` specs with `assigns` don't work because Inertia uses a custom render pipeline that only executes in the full request cycle.
### Direct Access (use matchers above when possible)
```ruby
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`](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.More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.

