Tara Livesey
Tara Livesey

Reputation: 23

Testing environment dependent routing with RSPEC

I have some routes that should only exist in the development environment but I can't get the specs for this working. Any ideas what I am doing wrong? I initially tried this in the controller spec but then realised I need :type => :routing to use be_routable so have separated this out. I have debugged and Rails.env has the values I expect in each context. Do I need to reload the routes? Have tried to do this but can't find a syntax that rspec is happy with...

Snippet from routes.rb:

    resources :users do
        collection do
          ...
          if Rails.env.development?
            get :new_development_account
            post :create_development_account
          end
        end
        ...

Routing spec:

require 'spec_helper'

describe "routes for users controller", :type => :routing do

    context "production environment" do
      it "development routes do not exist" do
        allow(Rails).to receive(:env) { "production".inquiry }
        expect(:get => "/ims/users/new_development_account").not_to be_routable
        expect(:post => "/ims/users/create_development_account").not_to be_routable
      end
    end

    context "development environment" do
      it "development routes exist" do
        allow(Rails).to receive(:env) { "development".inquiry }
        expect(:get => "/ims/users/new_development_account").to be_routable
        expect(:post => "/ims/users/create_development_account").to be_routable
      end
    end
end

What is particularly odd is that it fails both tests:

Failures:

  1) routes for users controller production environment development routes do not exist
     Failure/Error: expect(:get => "/ims/users/new_development_account").not_to be_routable
       expected {:get=>"/ims/users/new_development_account"} not to be routable, but it routes to {:action=>"show", :controller=>"ims/users", :id=>"new_development_account"}
     # /var/code/bundle/ruby/2.2.0/gems/given_core-3.5.4/lib/given/rspec/monkey.rb:31:in `handle_matcher'
     # ./spec/routing/users_controller_spec.rb:9:in `block (3 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:18:in `block (2 levels) in <top (required)>'

  2) routes for users controller development environment development routes exist
     Failure/Error: expect(:post => "/ims/users/create_development_account").to be_routable
       expected {:post=>"/ims/users/create_development_account"} to be routable
     # /var/code/bundle/ruby/2.2.0/gems/given_core-3.5.4/lib/given/rspec/monkey.rb:21:in `handle_matcher'
     # ./spec/routing/users_controller_spec.rb:18:in `block (3 levels) in <top (required)>'
     # ./spec/support/database_cleaner.rb:18:in `block (2 levels) in <top (required)>'

Upvotes: 2

Views: 1261

Answers (3)

josegomezr
josegomezr

Reputation: 916

It's been ~4 years already but we've hit this very same setup and ended up with an alternative solution that does not alter the global state [that much] of the app.

As @Vasfed explained:

Routes are loaded once on app start, and Rails.env will be 'test'.

In our case we run tests with rspec_parallel and had a toggle that would only load ~20% of the routes, and parallel tests would fail at random because the routes suddenly vanished.

Because of this we did Opt 2 (have the routes blocked by a dynamic constraint) for a while until finding out that Rails.application.routes is somewhat easy to fabricate [probably at the expense of CI time].

So, if you attempt something along the lines of:

require 'spec_helper'

describe "routes for users controller", :type => :routing do
  context "production environment" do
    before { allow(Rails).to receive(:env) { "production".inquiry } }
    routes do
      ActionDispatch::Routing::RouteSet.new_with_config(Rails.application.config)
    end

    it "development routes do not exist" do
      
      expect(:get => "/ims/users/new_development_account").not_to be_routable
      expect(:post => "/ims/users/create_development_account").not_to be_routable
    end
  end

  context "development environment" do
    before { allow(Rails).to receive(:env) { "development".inquiry } }
    routes do
      ActionDispatch::Routing::RouteSet.new_with_config(Rails.application.config)
    end

    it "development routes exist" do
      expect(:get => "/ims/users/new_development_account").to be_routable
      expect(:post => "/ims/users/create_development_account").to be_routable
    end
  end
end

You'd be able to assert different routes in different envs. Keep in mind though:

  • #routes call executes must be as close to the example as possible, it's called in order of usage. In the example at hand:

    1. #context
    2. #before
    3. #routes
    4. #it

    If you move the routes section outside to the global scope it'll run before any other block:

    1. #routes
    2. #context
    3. #before
    4. #it

Upvotes: 0

rya brody
rya brody

Reputation: 286

The routes can be reloaded with Rails.application.reload_routes!.

Example:

it "does not have routes in production" do
  allow(Rails).to receive(:env) { "production".inquiry }
  Rails.application.reload_routes!
  expect(:get => "/ims/users/new_development_account").not_to be_routable
  expect(:post => "/ims/users/create_development_account").not_to be_routable
end

Upvotes: 2

Vasfed
Vasfed

Reputation: 18444

Routes are loaded once on app start, and Rails.env will be 'test'.

Usually it's better to keep you development environment as close to production as possible, including routes.

If you want some shortcuts in dev env, there're options:

  1. You can use regular /ims/users/new and create actions with a parameter that will only have effect in development. For example /ims/users/new?development=true that will also render additional hidden field to pass state to create
  2. Use dynamic constraints on routes (this way routes will still be listed in production/test, but not accessible)
  3. Just leave the routes as always present and raise an error in controller for wrong environments
  4. Extract whole development_account-feature into an engine, test separately and mount it only in development (the hardest way, not always possible)

I'd go with first option, it's both simple, incapsulates relatively well and most probably your *_development_account mimic corresponding actions anyway

Upvotes: 0

Related Questions