inertia-rails-testing by inertia-rails/skills
npx skills add https://github.com/inertia-rails/skills --skill inertia-rails-testing使用 RSpec 和 Minitest 测试 Inertia 响应的模式。
针对每个控制器动作,验证:
render_component('users/index')have_props(users: satisfy { ... })have_no_prop(:secret)follow_redirect! 然后 have_flash(notice: '...')have_deferred_props(:analytics)常见错误: 在 PRG(Post-Redirect-Get)模式后忘记 follow_redirect! —— 没有它,你是在断言 302 重定向响应,而不是后续的 Inertia 页面。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
# spec/rails_helper.rb
require 'inertia_rails/rspec'
| 匹配器 | 用途 |
|---|---|
be_inertia_response | 验证响应是否为 Inertia 格式 |
render_component('path') | 检查渲染的组件名称 |
have_props(key: value) | 部分属性匹配 |
have_exact_props(key: value) | 精确属性匹配 |
have_no_prop(:key) | 断言属性不存在 |
have_flash(key: value) | 部分闪存匹配 |
have_exact_flash(key: value) | 精确闪存匹配 |
have_no_flash(:key) | 断言闪存不存在 |
have_deferred_props(:key) | 检查延迟属性是否存在 |
have_view_data(key: value) | 部分视图数据匹配 |
始终使用匹配器(render_component、have_props、have_flash),而不是直接访问属性(inertia.component、inertia.props[:key]):
# 错误 — 直接属性访问:
# expect(inertia.component).to eq('users/index')
# expect(inertia.props[:users].length).to eq(3)
# 正确 — 使用匹配器:
expect(inertia).to render_component('users/index')
expect(inertia).to have_props(users: satisfy { |u| u.length == 3 })
# 关键模式:在 POST/PATCH/DELETE 之后,断言之前使用 follow_redirect!
it '成功时重定向并显示闪存消息' do
post users_path, params: { user: valid_params }
expect(response).to redirect_to(users_path)
follow_redirect!
expect(inertia).to have_flash(notice: '用户已创建!')
end
it '失败时返回验证错误' do
post users_path, params: { user: { name: '' } }
follow_redirect!
expect(inertia).to have_props(errors: hash_including('name' => anything))
end
来自 inertia_share 的共享属性包含在 inertia.props 中。在引入 inertia_rails/rspec 后,inertia 辅助方法在 type: :request 规格中可用:
it '包含共享的认证数据' do
sign_in(user)
get dashboard_path
expect(inertia).to have_props(
auth: hash_including(user: hash_including(id: user.id))
)
end
it '对未认证用户排除认证数据' do
get dashboard_path
expect(inertia).to have_props(auth: hash_including(user: nil))
end
it '延迟加载昂贵的分析数据' do
get dashboard_path
expect(inertia).to have_deferred_props(:analytics, :statistics)
expect(inertia).to have_deferred_props(:slow_data, group: :slow)
end
it '支持部分重新加载' do
get users_path
# 模拟部分重新加载 — 仅获取特定属性
inertia_reload_only(:users, :pagination)
# 或排除特定属性
inertia_reload_except(:expensive_stats)
# 加载延迟属性
inertia_load_deferred_props(:default)
inertia_load_deferred_props # 加载所有组
end
it '通过 inertia_location 重定向到 Stripe' do
post checkout_path
# inertia_location 返回 409 状态码并带有 X-Inertia-Location 头部
expect(response).to have_http_status(:conflict)
expect(response.headers['X-Inertia-Location']).to match(/stripe\.com/)
end
<Form> 是否发送 CSRF 令牌或 router.visit 是否工作。测试你自己的控制器逻辑。have_props(users: satisfy { ... }),而不是对完整 JSON 进行深度相等比较。当你添加列时,脆弱的测试会中断。follow_redirect! 测试闪存 — 在带有重定向的 POST/PATCH/DELETE 之后,你必须在断言闪存或属性之前使用 follow_redirect!。没有它,你是在断言 302 响应,而不是页面。nil,因为它们是在页面渲染后的单独请求中获取的。使用 have_deferred_props(:key) 来验证它们已注册,或使用 inertia_load_deferred_props 在测试中解析它们。type: :request 规格。带有 assigns 的 type: :controller 规格不起作用,因为 Inertia 使用仅在完整请求周期中执行的自定义渲染管道。inertia.component # => 'users/index'
inertia.props # => { users: [...] }
inertia.props[:users] # 直接属性访问
inertia.flash # => { notice: '已创建!' }
inertia.deferred_props # => { default: [:analytics], slow: [:report] }
inertia-rails-controllers(属性类型、闪存、PRG)inertia-rails-forms(提交、验证)inertia-rails-pages(延迟、usePage)强制要求 — 编写 Minitest 测试(非 RSpec)时阅读整个文件:references/minitest.md(约 40 行)— 与上述 RSpec 匹配器等效的 Minitest 断言。
不要为 RSpec 项目加载 minitest.md — 上述匹配器就是你所需要的全部。
每周安装量
76
仓库
GitHub 星标数
35
首次出现
2026年2月13日
安全审计
安装于
gemini-cli75
opencode75
codex75
github-copilot74
amp73
kimi-cli72
Testing patterns for Inertia responses with RSpec and Minitest.
For each controller action, verify:
render_component('users/index')have_props(users: satisfy { ... })have_no_prop(:secret)follow_redirect! then have_flash(notice: '...')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.
# spec/rails_helper.rb
require 'inertia_rails/rspec'
| 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) |
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
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
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
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
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
<Form> sends CSRF tokens or that router.visit works. Test YOUR controller logic.have_props(users: satisfy { ... }), not deep equality on full JSON. Brittle tests break when you add a column.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.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.inertia.component # => 'users/index'
inertia.props # => { users: [...] }
inertia.props[:users] # direct prop access
inertia.flash # => { notice: 'Created!' }
inertia.deferred_props # => { default: [:analytics], slow: [:report] }
inertia-rails-controllers (prop types, flash, PRG)inertia-rails-forms (submission, validation)inertia-rails-pages (Deferred, usePage)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.
Weekly Installs
76
Repository
GitHub Stars
35
First Seen
Feb 13, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli75
opencode75
codex75
github-copilot74
amp73
kimi-cli72
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
44,900 周安装
| 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 |
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.