208 lines
6.2 KiB
Ruby
208 lines
6.2 KiB
Ruby
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`. In a test suite using
|
|
# Minitest + Shoulda, 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', to: 'posts#index'
|
|
# get '/posts/:id', to: 'posts#show'
|
|
# end
|
|
#
|
|
# You could choose to write tests for these routes alongside other tests
|
|
# for PostsController:
|
|
#
|
|
# class PostsController < ApplicationController
|
|
# # ...
|
|
# end
|
|
#
|
|
# # RSpec
|
|
# RSpec.describe PostsController, type: :controller do
|
|
# it { should route(:get, '/posts').to(action: :index) }
|
|
# it { should route(:get, '/posts/1').to(action: :show, id: 1) }
|
|
# end
|
|
#
|
|
# # Minitest (Shoulda)
|
|
# 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', type: :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
|
|
#
|
|
# # Minitest (Shoulda)
|
|
# 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.
|
|
#
|
|
# #### Specifying a port
|
|
#
|
|
# If the route you're testing has a constraint on it that limits the route
|
|
# to a particular port, you can specify it by passing a `port` option to
|
|
# the matcher:
|
|
#
|
|
# class PortConstraint
|
|
# def initialize(port)
|
|
# @port = port
|
|
# end
|
|
#
|
|
# def matches?(request)
|
|
# request.port == @port
|
|
# end
|
|
# end
|
|
#
|
|
# My::Application.routes.draw do
|
|
# get '/posts',
|
|
# to: 'posts#index',
|
|
# constraints: PortConstraint.new(12345)
|
|
# end
|
|
#
|
|
# # RSpec
|
|
# describe 'Routing', type: :routing do
|
|
# it do
|
|
# should route(:get, '/posts', port: 12345).
|
|
# to('posts#index')
|
|
# end
|
|
# end
|
|
#
|
|
# # Minitest (Shoulda)
|
|
# class RoutesTest < ActionController::IntegrationTest
|
|
# should route(:get, '/posts', port: 12345).
|
|
# to('posts#index')
|
|
# end
|
|
#
|
|
# #### Qualifiers
|
|
#
|
|
# ##### to
|
|
#
|
|
# Use `to` to specify the action (along with the controller, if needed)
|
|
# that the route resolves to.
|
|
#
|
|
# `to` takes either keyword arguments (`controller` and `action`) or a
|
|
# string that represents the controller/action pair:
|
|
#
|
|
# 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)
|
|
#
|
|
# You may also specify special parameters such as `:format`:
|
|
#
|
|
# route(:get, '/posts').to('posts#index', format: :json)
|
|
#
|
|
# @return [RouteMatcher]
|
|
#
|
|
def route(method, path, port: nil)
|
|
RouteMatcher.new(self, method, path, port: port)
|
|
end
|
|
|
|
# @private
|
|
class RouteMatcher
|
|
def initialize(context, method, path, port: nil)
|
|
@context = context
|
|
@method = method
|
|
@path = add_port_to_path(normalize_path(path), port)
|
|
@params = {}
|
|
end
|
|
|
|
attr_reader :failure_message
|
|
|
|
def to(*args)
|
|
@params = RouteParams.new(args).normalize
|
|
self
|
|
end
|
|
|
|
def in_context(context)
|
|
@context = context
|
|
self
|
|
end
|
|
|
|
def matches?(controller)
|
|
guess_controller_if_necessary(controller)
|
|
|
|
route_recognized?
|
|
end
|
|
|
|
def description
|
|
"route #{method.to_s.upcase} #{path} to/from #{params.inspect}"
|
|
end
|
|
|
|
def failure_message_when_negated
|
|
"Didn't expect to #{description}"
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :context, :method, :path, :params
|
|
|
|
def normalize_path(path)
|
|
if path.start_with?('/')
|
|
path
|
|
else
|
|
"/#{path}"
|
|
end
|
|
end
|
|
|
|
def add_port_to_path(path, port)
|
|
if port
|
|
"https://example.com:#{port}" + path
|
|
else
|
|
path
|
|
end
|
|
end
|
|
|
|
def guess_controller_if_necessary(controller)
|
|
params[:controller] ||= controller.controller_path
|
|
end
|
|
|
|
def route_recognized?
|
|
context.send(
|
|
:assert_routing,
|
|
{ method: method, path: path },
|
|
params,
|
|
)
|
|
true
|
|
rescue ::ActionController::RoutingError => e
|
|
@failure_message = e.message
|
|
false
|
|
rescue Shoulda::Matchers.assertion_exception_class => e
|
|
@failure_message = e.message
|
|
false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|