How should I test routes and controllers with rspec?
Solution 1
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
Solution 2
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.
Starkers
Updated on August 02, 2022Comments
-
Starkers almost 2 years
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