我应该如何使用rspec测试路由和控制器?

时间:2013-11-25 15:50:07

标签: ruby-on-rails rspec capybara

我只有一个规范,位于spec/controllers/statuses_spec.rb

以下是其内容:

require 'spec_helper'

describe StatusesController do
    describe "routing" do

    it "routes to #index" do
        get("/statuses").should route_to("statuses#index")
    end

  end
end

可以说,我有一个简单的状态脚手架,状态控制器具有CRUD的标准操作,包括索引操作。

但是,我在运行上述测试时遇到了这个问题:

15:39:52 - INFO - Running: ./spec/controllers/statuses_spec.rb:6
Run options: include {:locations=>{"./spec/controllers/statuses_spec.rb"=>[6]}}
F

Failures:

  1) StatusesController routing routes to #index
     Failure/Error: get("/statuses").should route_to("statuses#index")
     ActionController::UrlGenerationError:
       No route matches {:controller=>"statuses", :action=>"/statuses"}
     # ./spec/controllers/statuses_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 0.21772 seconds
1 example, 1 failure

Rspec假设我正在处理statuses控制器,我觉得这很直观,因为我在规范的描述块中引用了它,它认为我已经传入了get的字符串method('/ statuses')就是函数。

坦率地说,我真的不喜欢这个。我希望能够测试URL栏中的确切字符串是否转到右侧控制器#action对。无论如何,我按照rspec说并做到这一点:

require 'spec_helper'

describe StatusesController do
    describe "routing" do

    it "routes to #index" do
        get("index").should route_to("statuses#index")
    end

  end
end

然而,现在我明白了:

Run options: include {:locations=>{"./spec/controllers/statuses_spec.rb"=>[6]}}
F

Failures:

  1) StatusesController routing routes to #index
     Failure/Error: get("index").should route_to("statuses#index")
     NoMethodError:
       undefined method `values' for #<ActionController::TestResponse:0x00000102bd3208>
     # ./spec/controllers/statuses_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 0.31019 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/statuses_spec.rb:6 # StatusesController routing routes to #index

我收到关于values方法的无方法错误。值?说真的,到底是什么?我不知道为什么我会收到这个错误。这是我的规范助手:

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'capybara/rspec'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  # ## Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    Capybara.run_server = true
    Capybara.javascript_driver = :webkit
    Capybara.default_selector = :css
    Capybara.server_port = 7171
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  config.include RSpec::Rails::RequestExampleGroup, type: :feature

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

  # Run specs in random order to surface order dependencies. If you find an
  # order dependency and want to debug it, you can fix the order by providing
  # the seed, which is printed after each run.
  #     --seed 1234
  config.order = "random"
end

3 个答案:

答案 0 :(得分:8)

测试路线,尤其是标准的RESTful路线,不是标准做法。

a)你不想浪费时间重新测试Rails&#39;路由功能

b)您的控制器或request规范在无法路由请求时会失败

更常见的是,编写和维护路由测试不会带来太多价值和增加信心。 当它们变得复杂且容易出错时,请考虑测试路线。

也就是说,RSpec提供route_to matcher来指定请求是可路由的。

建议的路由规范位置在spec/routing下,但在控制器规范中查看路由规范的情况并不少见。例如

describe VersionsController do
  describe 'routing' do
    it 'routes GET /version to VersionsController#show' do
      expect(get: '/version').to route_to(controller: 'versions', action: 'show')
    end
  end
end

shoulda-matchers gem有自己的路径匹配器,允许您编写测试,例如

describe PostsController do
  it { should route(:get, '/posts').to(action: :index) }
  it { should route(:get, '/posts/1').to(action: :show, id: 1) }
end

答案 1 :(得分:1)

Routes should be done as part of integration tests. Integration tests are where you test the important work flows of your application - more specifically whether a URL is defined or not seems to be an important workflow.

Your integration test would look like any normal integration test:

require 'test_helper'
class RoutesTest < ActionController::IntegrationTest
   test "route test" do
       assert_generates "/videos/5", { :controller => "videos", :action => "show", :id => "1" }
       assert_generates "/about", :controller => "pages", :action => "about"
   end
end

As to @jemminger's response of not testing routes - While it is Rail's tests that verify that routes.rb works, it's not Rail's responsibility to test whether http://yoursite.com/users is defined in your routes. The caveat is that most route testing could be done in existing integration tests, so specific tests for routes could be redundant.

The specific use case I can think of are all the people that have already, or are going to upgrade from Rails 2 to Rails 3. The code to define routes has changed significantly, and it's better to find out from tests that the routes were upgraded correctly, than from users when they report 404 errors.

答案 2 :(得分:0)

it { expect(put: "/posts/1").to route_to(controller: "posts", action: "update", id: "1") }

it { expect(GET: "/posts/1").to route_to(controller: 'posts', action: 'show', id: "1") }

if { expect(get: '/posts').to route_to(controller: 'posts', action: 'index') }

如果没有删除路径

it { expect(delete: "/posts/1").to_not be_routable }