为什么 rspec 找不到我的无参数路由?

Why can't rspec find my no-params-required route?

背景

我正在尝试将旧的 ActionDispatch::IntegrationTest 测试移植到 rspec。

此测试当前通过:

require 'test_helper'

class HeartbeatControllerTest < ActionDispatch::IntegrationTest
  test "heartbeat" do
    get "/heartbeat"
    assert_response :success
  end
end

控制器

我有一个 HeartbeatController 其中 returns 一个 200:

class HeartbeatController < ApplicationController
  def show
    render(json: { 'status': 'ok' })
  end
end

curl 配合使用效果很好。

路线

rake routes 显示路线:

                               Prefix Verb   URI Pattern                                                                              Controller#Action
                            heartbeat GET    /heartbeat(.:format)                                                                     heartbeat#show

规格

我的测试非常简单:

require 'rails_helper'

describe HeartbeatController do
  it 'succeeds' do
    get '/heartbeat'
    expect(response).to be_success
  end
end

但是当我运行它

Failures:

  1) HeartbeatController succeeds
     Failure/Error: get '/heartbeat'

     ActionController::UrlGenerationError:
       No route matches {:action=>"/heartbeat", :controller=>"heartbeat"}
     # ./spec/controllers/heartbeat_controller_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.00718 seconds (files took 1.11 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/heartbeat_controller_spec.rb:4 # HeartbeatController succeeds

问题

如果这是控制器规范,您希望传递方法的名称而不是路由。

require 'rails_helper'

describe HeartbeatController do
  it 'succeeds' do
    get :show
    expect(response).to be_success
  end
end

控制器规范实际上并不驱动整个 rails 堆栈 - 它们只是创建一个模拟请求并将其传递给控制器​​的实例并调用控制器上的方法。

您收到路由错误的原因是 ActionController::TestCase::Behavior 尝试验证给定的 controller/method 名称组合是否存在路由。因此 No route matches {:action=>"/heartbeat", :controller=>"heartbeat"}.

众所周知,单元测试控制器的整个过程存在严重缺陷,因为您模拟了框架的大部分内容,而不是实际测试控制器的功能——它们响应 HTTP 请求。由于路由层和中间件堆栈是模拟出来的,因此您的测试不会像真正的集成测试那样覆盖它们。

实际上驱动整个堆栈的Rails and RSpec team discourage directly testing controllers. Instead use request specs

# spec/requests/heartbeats_spec.rb
require 'rails_helper'

describe 'Heartbeats', type: :request do
  describe "GET /heartbeat" do
    it 'succeeds' do
      get '/heartbeat'
      expect(response).to be_successful
    end 
  end
end