简体   繁体   中英

How should I test routes and controllers with rspec?

I have just one spec, located at spec/controllers/statuses_spec.rb

Here is its contents:

require 'spec_helper'

describe StatusesController do
    describe "routing" do

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

  end
end

Suffice to say, I have a simple statuses scaffold, and the statuses controller has the standard actions for CRUD, including an index action.

However, I get this failure when running the above test:

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 makes the assumption that I'm dealing with the statuses controller, which is sort of intuitive I guess because I referenced it in my spec's describe block, and it thinks the string I've passed into the get method ('/statuses') is the function.

Frankly I don't really like this. I want to be able to test the exact string that is in the URL bar is going to the right controller#action pair. Regardless, I do as rspec says and do this:

require 'spec_helper'

describe StatusesController do
    describe "routing" do

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

  end
end

However, now I get this:

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

I'm getting a no method error regarding a values method. Values? Seriously, just what? I have no idea why I'm getting this error. Here's my spec helper:

# 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

Testing routes, especially standard RESTful routes, is not standard practice.

a) You don't want to waste effort retesting Rails' routing functionality

b) Your controller or request specs should fail when they cannot route a request

More often that not, writing and maintaining routing tests does not give much value and increased confidence. Consider testing routes when they become complex and error-prone.

That said, RSpec provides a route_to matcher for specifying that a request is routable.

The recommended place for your routing specs is under spec/routing , though it's not uncommon to see routing specs alongside controller specs. For example

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

The shoulda-matchers gem has its own route matcher, allowing you to write tests such as

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

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.

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') }

If there is no routes for delete

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM