module Shoulda module Matchers module ActionController # The `route` matcher tests that a route resolves to a controller, # action, and params; and that the controller, action, and params # generates the same route. For an RSpec suite, this is like using a # combination of `route_to` and `be_routable`. For a Test::Unit suite, it # provides a more expressive syntax over `assert_routing`. # # You can use this matcher either in a controller test case or in a # routing test case. For instance, given these routes: # # My::Application.routes.draw do # get '/posts', controller: 'posts', action: 'index' # get '/posts/:id' => 'posts#show' # end # # You could choose to write tests for these routes alongside other tests # for PostsController: # # class PostsController < ApplicationController # # ... # end # # # RSpec # describe PostsController do # it { should route(:get, '/posts').to(action: :index) } # it { should route(:get, '/posts/1').to(action: :show, id: 1) } # end # # # Test::Unit # class PostsControllerTest < ActionController::TestCase # should route(:get, '/posts').to(action: 'index') # should route(:get, '/posts/1').to(action: :show, id: 1) # end # # Or you could place the tests along with other route tests: # # # RSpec # describe 'Routing' do # it do # should route(:get, '/posts'). # to(controller: :posts, action: :index) # end # # it do # should route(:get, '/posts/1'). # to('posts#show', id: 1) # end # end # # # Test::Unit # class RoutesTest < ActionController::IntegrationTest # should route(:get, '/posts'). # to(controller: :posts, action: :index) # # should route(:get, '/posts/1'). # to('posts#show', id: 1) # end # # Notice that in the former case, as we are inside of a test case for # PostsController, we do not have to specify that the routes resolve to # this controller. In the latter case we specify this using the # `controller` key passed to the `to` qualifier. # # #### Qualifiers # # ##### to # # Use `to` to specify the action (along with the controller, if needed) # that the route resolves to. # # # Three ways of saying the same thing (using the example above) # route(:get, '/posts').to(action: index) # route(:get, '/posts').to(controller: :posts, action: index) # route(:get, '/posts').to('posts#index') # # If there are parameters in your route, then specify those too: # # route(:get, '/posts/1').to('posts#show', id: 1) # # @return [RouteMatcher] # def route(method, path) RouteMatcher.new(method, path, self) end # @private class RouteMatcher def initialize(method, path, context) @method = method @path = path @context = context end attr_reader :failure_message, :failure_message_when_negated alias failure_message_for_should failure_message alias failure_message_for_should_not failure_message_when_negated def to(*args) @params = RouteParams.new(args).normalize self end def in_context(context) @context = context self end def matches?(controller) guess_controller!(controller) route_recognized? end def description "route #{@method.to_s.upcase} #{@path} to/from #{@params.inspect}" end private def guess_controller!(controller) @params[:controller] ||= controller.controller_path end def route_recognized? begin @context.__send__(:assert_routing, { method: @method, path: @path }, @params) @failure_message_when_negated = "Didn't expect to #{description}" true rescue ::ActionController::RoutingError => error @failure_message = error.message false rescue Shoulda::Matchers::AssertionError => error @failure_message = error.message false end end end end end end