inertia-rails-testing
Original:🇺🇸 English
Translated
Test Inertia Rails applications with RSpec or Minitest. Use when writing tests for Inertia responses, components, props, flash messages, and partial reloads.
2installs
Added on
NPX Install
npx skill4agent add cole-robertson/inertia-rails-skills inertia-rails-testingTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Inertia Rails Testing
Comprehensive guide to testing Inertia Rails applications with RSpec and Minitest.
Testing Approaches
- Endpoint tests - Verify server-side Inertia responses
- Client-side unit tests - Test components with Vitest/Jest
- End-to-end tests - Full browser testing with Capybara
RSpec Setup
Add to :
spec/rails_helper.rbruby
require 'inertia_rails/rspec'RSpec Matchers Reference
be_inertia_response
be_inertia_responseVerifies the response is an Inertia response:
ruby
it 'returns an Inertia response' do
get users_path
expect(inertia).to be_inertia_response
endrender_component
render_componentChecks 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')
endhave_props
have_propsPartial 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)
endhave_exact_props
have_exact_propsExact 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'
)
endhave_flash
have_flashCheck 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')
endhave_deferred_props
have_deferred_propsVerify 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
endComplete 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
endTesting 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
endTesting 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
endTesting 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
endMinitest Setup
Add to :
test/test_helper.rbruby
require 'inertia_rails/minitest'Minitest Assertions Reference
| RSpec Matcher | Minitest Assertion |
|---|---|
| |
| |
| |
| |
| |
| |
Negation: variants available.
refute_*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
endEnd-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
endClient-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
end3. 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])
)
)
end4. Test Error States
ruby
it 'handles not found' do
get user_path(id: 'nonexistent')
expect(response).to have_http_status(:not_found)
end5. 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