module ActionDispatch::Assertions::RoutingAssertions

def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)

assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
# Asserts that the generated route gives us our custom route

assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
# Tests the generation of a route with a parameter

assert_generates "/items/list", controller: "items", action: "list"
# Tests that the list action is properly routed

assert_generates "/items", controller: "items", action: "index"
# Asserts that the default action is generated for a route with no action

The `defaults` parameter is unused.

custom error message for assertion failures.
would be in a query string. The `message` parameter allows you to specify a
tell the request the names and values of additional request parameters that
This is the inverse of `assert_recognizes`. The `extras` parameter is used to
Asserts that the provided options can be used to generate the provided path.
def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil)
  if expected_path.include?("://")
    fail_on(URI::InvalidURIError, message) do
      uri = URI.parse(expected_path)
      expected_path = uri.path.to_s.empty? ? "/" : uri.path
    end
  else
    expected_path = "/#{expected_path}" unless expected_path.start_with?("/")
  end
  options = options.clone
  generated_path, query_string_keys = @routes.generate_extras(options, defaults)
  found_extras = options.reject { |k, _| ! query_string_keys.include? k }
  msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
  assert_equal(extras, found_extras, msg)
  msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
      expected_path)
  assert_equal(expected_path, generated_path, msg)
end

def assert_recognizes(expected_options, path, extras = {}, msg = nil)

assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
# Test a custom route

assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
# Test an action with a parameter

assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
# Test a specific action

assert_recognizes({controller: 'items', action: 'index'}, 'items')
# Check the default route (i.e., the index action)

displayed upon failure.
The `message` parameter allows you to pass in an error message that is

assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
# Asserts that a path of '/items/list/1?view=print' returns the correct options

path directly will not work. For example:
you must use the extras argument because appending the query string on the
query string will end up in the params hash correctly. To test query strings
normally be in the query string. This can be used to assert that values in the
You can also pass in `extras` with a hash containing URL parameters that would

assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
# Asserts that POSTing to /items will call the create action on ItemsController

the required HTTP verb.
contain a `:path` with the incoming request path and a `:method` containing
This is useful for routes requiring a specific HTTP method. The hash should
Pass a hash in the second argument (`path`) to specify the request method.

`expected_options`.
Basically, it asserts that Rails recognizes the route given by
the parsed options (given in the `expected_options` hash) match `path`.
Asserts that the routing of the given `path` was handled correctly and that
def assert_recognizes(expected_options, path, extras = {}, msg = nil)
  if path.is_a?(Hash) && path[:method].to_s == "all"
    [:get, :post, :put, :delete].each do |method|
      assert_recognizes(expected_options, path.merge(method: method), extras, msg)
    end
  else
    request = recognized_request_for(path, extras, msg)
    expected_options = expected_options.clone
    expected_options.stringify_keys!
    msg = message(msg, "") {
      sprintf("The recognized options <%s> did not match <%s>, difference:",
              request.path_parameters, expected_options)
    }
    assert_equal(expected_options, request.path_parameters, msg)
  end
end

def assert_routing(path, options, defaults = {}, extras = {}, message = nil)

assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
# Tests a route with an HTTP method

assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
# Tests a route, providing a defaults hash

assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
# Asserts a basic route (controller + default action), with an error message if it fails

assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
# Test a route generated with a specific controller, action, and parameter (id)

assert_routing '/home', controller: 'home', action: 'index'
# Asserts a basic route: a controller with the default action (index)

to specify a custom error message to display upon failure.
provided as a query string to the action. The `message` parameter allows you
The `extras` hash allows you to specify options that would normally be

essentially combines `assert_recognizes` and `assert_generates` into one step.
that `path` generates `options` and then that `options` generates `path`. This
Asserts that path and options match both ways; in other words, it verifies
def assert_routing(path, options, defaults = {}, extras = {}, message = nil)
  assert_recognizes(options, path, extras, message)
  controller, default_controller = options[:controller], defaults[:controller]
  if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
    options[:controller] = "/#{controller}"
  end
  generate_options = options.dup.delete_if { |k, _| defaults.key?(k) }
  assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
end

def create_routes(config = nil)

def create_routes(config = nil)
  @routes = ActionDispatch::Routing::RouteSet.new(config || ActionDispatch::Routing::RouteSet::DEFAULT_CONFIG)
  if @controller
    @controller = @controller.clone
    _routes = @routes
    @controller.singleton_class.include(_routes.url_helpers)
    if @controller.respond_to? :view_context_class
      view_context_class = Class.new(@controller.view_context_class) do
        include _routes.url_helpers
      end
      custom_view_context = Module.new {
        define_method(:view_context_class) do
          view_context_class
        end
      }
      @controller.extend(custom_view_context)
    end
  end
  yield @routes
end

def fail_on(exception_class, message)

def fail_on(exception_class, message)
  yield
rescue exception_class => e
  raise Minitest::Assertion, message || e.message
end

def method_missing(selector, ...)

ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, ...)
  if @controller && @routes&.named_routes&.route_defined?(selector)
    @controller.public_send(selector, ...)
  else
    super
  end
end

def recognized_request_for(path, extras = {}, msg)

Recognizes the route for a given path.
def recognized_request_for(path, extras = {}, msg)
  if path.is_a?(Hash)
    method = path[:method]
    path   = path[:path]
  else
    method = :get
  end
  controller = @controller if defined?(@controller)
  request = ActionController::TestRequest.create controller&.class
  if path.include?("://")
    fail_on(URI::InvalidURIError, msg) do
      uri = URI.parse(path)
      request.env["rack.url_scheme"] = uri.scheme || "http"
      request.host = uri.host if uri.host
      request.port = uri.port if uri.port
      request.path = uri.path.to_s.empty? ? "/" : uri.path
    end
  else
    path = "/#{path}" unless path.start_with?("/")
    request.path = path
  end
  request.request_method = method if method
  params = fail_on(ActionController::RoutingError, msg) do
    @routes.recognize_path(path, method: method, extras: extras)
  end
  request.path_parameters = params.with_indifferent_access
  request
end

def reset_routes(old_routes, old_controller)

def reset_routes(old_routes, old_controller)
  @routes = old_routes
  if @controller
    @controller = old_controller
  end
end

def setup # :nodoc:

:nodoc:
def setup # :nodoc:
  @routes ||= nil
  super
end

def with_routing(config = nil, &block)


end
assert_equal "/users", users_path
end
resources :users
set.draw do
with_routing do |set|

create some routes using `set.draw { match ... }`:
The new instance is yielded to the passed block. Typically the block will

temporarily replaces @routes with a new RouteSet instance.
A helper to make it easier to test different route configurations. This method
def with_routing(config = nil, &block)
  old_routes, old_controller = @routes, @controller
  create_routes(config, &block)
ensure
  reset_routes(old_routes, old_controller)
end