inertia-rails-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inertia Rails Testing

Inertia Rails 测试

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.
使用RSpec和Minitest测试Inertia响应的模式。
针对每个控制器动作,需验证:
  • 正确的组件
    render_component('users/index')
  • 预期的属性
    have_props(users: satisfy { ... })
  • 无数据泄露
    have_no_prop(:secret)
  • Flash消息 → 先调用
    follow_redirect!
    ,再使用
    have_flash(notice: '...')
  • 延迟属性
    have_deferred_props(:analytics)
常见错误: PRG模式(Post/Redirect/Get)后忘记调用
follow_redirect!
——如果不调用,你是在对302重定向响应而非后续的Inertia页面进行断言。

Setup

设置

ruby
undefined
ruby
undefined

spec/rails_helper.rb

spec/rails_helper.rb

require 'inertia_rails/rspec'
undefined
require 'inertia_rails/rspec'
undefined

RSpec Matchers

RSpec 匹配器

MatcherPurpose
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
匹配器用途
be_inertia_response
验证响应为Inertia格式
render_component('path')
检查渲染的组件名称
have_props(key: value)
属性部分匹配
have_exact_props(key: value)
属性完全匹配
have_no_prop(:key)
断言属性不存在
have_flash(key: value)
Flash消息部分匹配
have_exact_flash(key: value)
Flash消息完全匹配
have_no_flash(:key)
断言Flash消息不存在
have_deferred_props(:key)
检查延迟属性是否存在
have_view_data(key: value)
view_data部分匹配

RSpec Examples

RSpec 示例

ALWAYS use matchers (
render_component
,
have_props
,
have_flash
) instead of direct property access (
inertia.component
,
inertia.props[:key]
):
ruby
undefined
请始终使用匹配器
render_component
have_props
have_flash
),而非直接访问属性(
inertia.component
inertia.props[:key]
):
ruby
undefined

BAD — direct property access:

错误示例 —— 直接访问属性:

expect(inertia.component).to eq('users/index')

expect(inertia.component).to eq('users/index')

expect(inertia.props[:users].length).to eq(3)

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
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

核心模式:POST/PATCH/DELETE请求后,先调用follow_redirect!再进行断言

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
undefined
it '重定向并返回成功提示Flash' 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
undefined

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
来自
inertia_share
的共享属性会包含在
inertia.props
中。引入
inertia_rails/rspec
后,
inertia
辅助方法可在
type: :request
测试中使用:
ruby
it '包含共享的认证数据' do
  sign_in(user)
  get dashboard_path

  expect(inertia).to have_props(
    auth: hash_including(user: hash_including(id: user.id))
  )
end
ruby
it '未认证用户不返回认证数据' 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
ruby
it '延迟加载耗时的分析数据' 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
ruby
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

Test External Redirects (inertia_location)

测试外部重定向(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
ruby
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

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.
  • Inertia框架本身的行为 —— 不要测试
    <Form>
    是否发送CSRF令牌,或
    router.visit
    是否正常工作。只需测试你自己的控制器逻辑。
  • 属性的精确结构 —— 使用
    have_props(users: satisfy { ... })
    ,而非对完整JSON进行深度相等断言。这种脆弱的测试会在你添加列时失效。
  • 未调用follow_redirect!时的Flash —— 在POST/PATCH/DELETE请求并重定向后,必须先调用
    follow_redirect!
    ,再断言Flash或属性。否则你是在对302响应而非目标页面进行断言。
  • 初始加载时的延迟属性值 —— 初始响应中延迟属性为
    nil
    ,因为它们会在页面渲染后通过单独请求获取。使用
    have_deferred_props(:key)
    验证它们已注册,或使用
    inertia_load_deferred_props
    在测试中解析它们。
  • 模拟Inertia响应 —— 使用
    type: :request
    测试来覆盖完整调用栈。
    type: :controller
    测试搭配
    assigns
    无法正常工作,因为Inertia使用自定义渲染管道,仅在完整请求周期中执行。

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] }
ruby
inertia.component       # => 'users/index'
inertia.props           # => { users: [...] }
inertia.props[:users]   # 直接访问属性
inertia.flash           # => { notice: '已创建!' }
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)
  • 控制器模式
    inertia-rails-controllers
    (属性类型、Flash、PRG)
  • 表单流程
    inertia-rails-forms
    (提交、验证)
  • 延迟/共享属性
    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.
必看 —— 编写Minitest测试时请完整阅读此文件(非RSpec测试):
references/minitest.md
(约40行)—— 与上述RSpec匹配器对应的Minitest断言方法。
请勿在RSpec项目中引入
minitest.md
——上述匹配器已完全满足需求。