inertia-rails-testing
Test Inertia Rails applications with RSpec or Minitest. Use when writing tests for Inertia responses, components, props, flash messages, and partial reloads.
Works with
---
name: inertia-rails-testing
description: Test Inertia Rails applications with RSpec or Minitest. Use when writing tests for Inertia responses, components, props, flash messages, and partial reloads.
license: MIT
---
# Inertia Rails Testing
Comprehensive guide to testing Inertia Rails applications with RSpec and Minitest.
## Testing Approaches
1. **Endpoint tests** - Verify server-side Inertia responses
2. **Client-side unit tests** - Test components with Vitest/Jest
3. **End-to-end tests** - Full browser testing with Capybara
## RSpec Setup
Add to `spec/rails_helper.rb`:
```ruby
require 'inertia_rails/rspec'
```
## RSpec Matchers Reference
### `be_inertia_response`
Verifies the response is an Inertia response:
```ruby
it 'returns an Inertia response' do
get users_path
expect(inertia).to be_inertia_response
end
```
### `render_component`
Checks the rendered component name:
```ruby
it 'renders the correct component' do
get users_path
expect(inertia).to render_component('users/index')
# Case-insensitive matching
expect(inertia).to render_component('Users/Index')
end
```
### `have_props`
Partial matching of props:
```ruby
it 'includes expected props' do
get user_path(user)
expect(inertia).to have_props(
user: hash_including(
id: user.id,
name: 'John'
)
)
end
# With RSpec matchers
it 'has users array' do
get users_path
expect(inertia).to have_props(
users: be_an(Array),
total: be > 0
)
end
# Check for specific keys
it 'includes required keys' do
get dashboard_path
expect(inertia).to have_props(:user, :stats, :notifications)
end
```
### `have_exact_props`
Exact matching of all props:
```ruby
it 'has exactly these props' do
get simple_page_path
expect(inertia).to have_exact_props(
title: 'Simple Page',
content: 'Hello'
)
end
```
### `have_flash`
Check flash messages:
```ruby
it 'shows success message' do
post users_path, params: { user: valid_attributes }
follow_redirect!
expect(inertia).to have_flash(notice: 'User created!')
end
it 'shows error message' do
delete user_path(admin_user)
follow_redirect!
expect(inertia).to have_flash(alert: 'Cannot delete admin')
end
```
### `have_deferred_props`
Verify deferred props:
```ruby
it 'defers analytics data' do
get dashboard_path
expect(inertia).to have_deferred_props(:analytics)
expect(inertia).to have_deferred_props(analytics: 'stats') # with group
end
```
### `have_no_prop`
Verify a prop is not present:
```ruby
it 'does not expose sensitive data' do
get user_path(user)
expect(inertia).to have_no_prop(:password_digest)
expect(inertia).to have_no_prop(:remember_token)
end
```
### `have_view_data` / `have_exact_view_data`
Check view data (server-side only data not sent to client):
```ruby
it 'includes view data' do
get users_path
expect(inertia).to have_view_data(total: 5)
expect(inertia).to have_exact_view_data(total: 5)
end
```
## Complete RSpec Examples
### Basic Request Specs
```ruby
# spec/requests/users_spec.rb
require 'rails_helper'
RSpec.describe '/users', type: :request do
let(:user) { create(:user) }
let(:valid_attributes) { { name: 'John', email: 'john@example.com' } }
let(:invalid_attributes) { { name: '', email: 'invalid' } }
describe 'GET /users' do
before { create_list(:user, 3) }
it 'renders the index component with users' do
get users_path
expect(inertia).to be_inertia_response
expect(inertia).to render_component('users/index')
expect(inertia).to have_props(
users: have_attributes(size: 3)
)
end
end
describe 'GET /users/:id' do
it 'renders the show component' do
get user_path(user)
expect(inertia).to render_component('users/show')
expect(inertia).to have_props(
user: hash_including(
id: user.id,
name: user.name,
email: user.email
)
)
end
it 'does not expose sensitive data' do
get user_path(user)
user_props = inertia.props[:user]
expect(user_props).not_to have_key(:password_digest)
expect(user_props).not_to have_key(:remember_token)
end
end
describe 'GET /users/new' do
it 'renders the new component' do
get new_user_path
expect(inertia).to render_component('users/new')
end
end
describe 'POST /users' do
context 'with valid parameters' do
it 'creates a new user and redirects' do
expect {
post users_path, params: { user: valid_attributes }
}.to change(User, :count).by(1)
expect(response).to redirect_to(users_url)
end
it 'shows success flash' do
post users_path, params: { user: valid_attributes }
follow_redirect!
expect(inertia).to have_flash(notice: /created/i)
end
end
context 'with invalid parameters' do
it 'does not create a user' do
expect {
post users_path, params: { user: invalid_attributes }
}.not_to change(User, :count)
end
it 'returns validation errors' do
post users_path, params: { user: invalid_attributes }
follow_redirect!
expect(inertia).to have_props(
errors: hash_including(:name, :email)
)
end
end
end
describe 'PATCH /users/:id' do
context 'with valid parameters' do
it 'updates the user' do
patch user_path(user), params: { user: { name: 'Updated' } }
expect(user.reload.name).to eq('Updated')
expect(response).to redirect_to(user_url(user))
end
end
context 'with invalid parameters' do
it 'returns validation errors' do
patch user_path(user), params: { user: { email: 'invalid' } }
follow_redirect!
expect(inertia).to have_props(errors: hash_including(:email))
end
end
end
describe 'DELETE /users/:id' do
it 'destroys the user' do
user # create the user
expect {
delete user_path(user)
}.to change(User, :count).by(-1)
expect(response).to redirect_to(users_url)
end
end
end
```
### Testing Shared Data
```ruby
# spec/requests/shared_data_spec.rb
RSpec.describe 'Shared data', type: :request do
describe 'authentication data' do
context 'when logged in' do
before { sign_in(user) }
it 'includes current user' do
get root_path
expect(inertia).to have_props(
auth: hash_including(
user: hash_including(id: user.id)
)
)
end
end
context 'when logged out' do
it 'has null user' do
get root_path
expect(inertia).to have_props(
auth: hash_including(user: nil)
)
end
end
end
end
```
### Testing Partial Reloads
```ruby
# spec/requests/partial_reloads_spec.rb
RSpec.describe 'Partial reloads', type: :request do
it 'supports partial reload of specific props' do
get dashboard_path
expect(inertia).to have_props(:users, :stats, :notifications)
# Simulate partial reload
inertia_reload_only(:users)
expect(inertia).to have_props(:users)
expect(inertia.props.keys).to eq([:users])
end
it 'supports excluding props' do
get dashboard_path
inertia_reload_except(:notifications)
expect(inertia).to have_props(:users, :stats)
expect(inertia).not_to have_props(:notifications)
end
end
```
### Testing Deferred Props
```ruby
# spec/requests/deferred_props_spec.rb
RSpec.describe 'Deferred props', type: :request do
it 'initially defers expensive data' do
get dashboard_path
expect(inertia).to have_deferred_props(:analytics)
expect(inertia.props).not_to have_key(:analytics)
end
it 'loads deferred props on request' do
get dashboard_path
inertia_load_deferred_props
expect(inertia).to have_props(
analytics: be_present
)
end
end
```
## Minitest Setup
Add to `test/test_helper.rb`:
```ruby
require 'inertia_rails/minitest'
```
## Minitest Assertions Reference
| RSpec Matcher | Minitest Assertion |
|---------------|-------------------|
| `be_inertia_response` | `assert_inertia_response` |
| `render_component` | `assert_inertia_component` |
| `have_props` | `assert_inertia_props` |
| `have_exact_props` | `assert_inertia_props_equal` |
| `have_flash` | `assert_inertia_flash` |
| `have_deferred_props` | `assert_inertia_deferred_props` |
| `have_no_prop` | `assert_no_inertia_prop` |
| `have_view_data` | `assert_inertia_view_data` |
Negation: `refute_*` variants available.
## Complete Minitest Examples
```ruby
# test/integration/users_test.rb
require 'test_helper'
class UsersTest < ActionDispatch::IntegrationTest
def setup
@user = users(:one)
end
test 'index renders users list' do
get users_path
assert_inertia_response
assert_inertia_component 'users/index'
assert_inertia_props users: ->(users) { users.is_a?(Array) }
end
test 'show renders user details' do
get user_path(@user)
assert_inertia_component 'users/show'
assert_inertia_props(
user: {
id: @user.id,
name: @user.name
}
)
end
test 'create with valid params redirects' do
assert_difference 'User.count', 1 do
post users_path, params: {
user: { name: 'New User', email: 'new@example.com' }
}
end
assert_redirected_to users_url
end
test 'create with invalid params shows errors' do
post users_path, params: { user: { name: '' } }
follow_redirect!
assert_inertia_props errors: { name: ["can't be blank"] }
end
test 'shows flash after successful create' do
post users_path, params: {
user: { name: 'Test', email: 'test@example.com' }
}
follow_redirect!
assert_inertia_flash notice: 'User created!'
end
test 'deferred props are loaded separately' do
get dashboard_path
assert_inertia_deferred_props :analytics
inertia_load_deferred_props
assert_inertia_props analytics: ->(data) { data.present? }
end
end
```
## Testing Configuration (v3)
### Evaluate Optional and Deferred Props
By default, optional and deferred props are not evaluated in tests. Enable evaluation for thorough testing:
```ruby
# spec/rails_helper.rb (RSpec)
InertiaRails::Testing.evaluate_optional_props = true
# test/test_helper.rb (Minitest)
InertiaRails::Testing.evaluate_optional_props = true
```
With this enabled, you can test the actual values of deferred and optional props:
```ruby
it 'returns analytics data when deferred props are loaded' do
InertiaRails::Testing.evaluate_optional_props = true
get dashboard_path
inertia_load_deferred_props
expect(inertia).to have_props(
analytics: hash_including(:visitors, :page_views)
)
end
```
## End-to-End Testing with Capybara
```ruby
# spec/system/users_spec.rb
require 'rails_helper'
RSpec.describe 'Users', type: :system do
before do
driven_by(:selenium_chrome_headless)
end
it 'creates a new user' do
visit new_user_path
fill_in 'Name', with: 'John Doe'
fill_in 'Email', with: 'john@example.com'
fill_in 'Password', with: 'password123'
click_button 'Create User'
expect(page).to have_content('User created successfully')
expect(page).to have_content('John Doe')
end
it 'shows validation errors' do
visit new_user_path
fill_in 'Name', with: ''
click_button 'Create User'
expect(page).to have_content("Name can't be blank")
end
it 'navigates without full page reload' do
visit users_path
# Capture initial page load marker
page.execute_script("window.initialPageLoad = true")
click_link 'New User'
# Still on same page load (SPA navigation)
expect(page.evaluate_script("window.initialPageLoad")).to be true
expect(page).to have_current_path(new_user_path)
end
end
```
## Client-Side Component Testing
### Vitest/Jest Setup for Vue
```javascript
// vitest.config.js
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
},
})
```
### Testing Vue Components
```javascript
// tests/pages/users/index.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UsersIndex from '@/pages/users/index.vue'
describe('UsersIndex', () => {
it('renders users list', () => {
const wrapper = mount(UsersIndex, {
props: {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
],
},
})
expect(wrapper.text()).toContain('John')
expect(wrapper.text()).toContain('Jane')
})
it('shows empty state when no users', () => {
const wrapper = mount(UsersIndex, {
props: { users: [] },
})
expect(wrapper.text()).toContain('No users found')
})
})
```
### Testing React Components
```javascript
// tests/pages/users/index.test.jsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import UsersIndex from '@/pages/users/index'
describe('UsersIndex', () => {
it('renders users list', () => {
render(
<UsersIndex
users={[
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
]}
/>
)
expect(screen.getByText('John')).toBeInTheDocument()
expect(screen.getByText('Jane')).toBeInTheDocument()
})
})
```
## Testing Best Practices
### 1. Test Response Structure, Not Implementation
```ruby
# Good - tests the contract
expect(inertia).to have_props(
user: hash_including(:id, :name, :email)
)
# Avoid - too coupled to implementation
expect(inertia.props[:user]).to eq(user.as_json)
```
### 2. Use Factories for Test Data
```ruby
# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
password { 'password123' }
end
end
```
### 3. Test Authorization Results
```ruby
it 'includes permission data' do
get users_path
expect(inertia).to have_props(
can: hash_including(
create_user: be_in([true, false])
)
)
end
```
### 4. Test Error States
```ruby
it 'handles not found' do
get user_path(id: 'nonexistent')
expect(response).to have_http_status(:not_found)
end
```
### 5. Use Shared Examples for Common Patterns
```ruby
RSpec.shared_examples 'requires authentication' do
it 'redirects to login' do
expect(response).to redirect_to(login_url)
end
end
describe 'GET /admin/users' do
context 'when not logged in' do
before { get admin_users_path }
it_behaves_like 'requires authentication'
end
end
```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.
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.
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.

