Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

anonymous controllers in integration specs #1596

Closed
oliviermilla opened this issue Apr 12, 2016 · 15 comments
Closed

anonymous controllers in integration specs #1596

oliviermilla opened this issue Apr 12, 2016 · 15 comments

Comments

@oliviermilla
Copy link

Hello,

I need to use anonymous controllers in a :request spec to test different API auth methods. As of rspec-rails 3.4.2, :request specs do not have access to the controllermethod as controller specs do. I think it makes sense to allow anonymouse controller in integration tests by default, even more considering that Rails 5 is leaning towards integrations tests vs controller tests.

What do you think?

@fables-tales
Copy link
Member

@muichkine were you previously (in the 3.0 series) able to use anonymous controllers in request specs? If so this is a regression. Otherwise we'll consider it as a feature request.

@JonRowe
Copy link
Member

JonRowe commented Apr 13, 2016

I don't think anonymous controllers were ever supposed to work in feature specs, and given that Rails is pushing towards no controller tests I don't think it makes sense to add anonymous controllers elsewhere, integration tests aren't supposed to be decoupled from implementation.

@fables-tales
Copy link
Member

@JonRowe we're not talking about :feature type specs here, we're talking about :request type specs (which rails calls integration tests). I'm sort of inclined to agree that given that :request specs don't know which controller they're talking to, we shouldn't add the ability to call controller in request specs.

@JonRowe
Copy link
Member

JonRowe commented Apr 13, 2016

My bad I meant feature and/or request specs.

@fables-tales
Copy link
Member

@muichkine can you expand on your use case?

@oliviermilla
Copy link
Author

I agree with the raised points.

It makes full sense that requestsare not bound to any controller and so anonymous controller makes no sense. It makes sense though to override several controllers for one reason or another.

controller specs on the other hand definitely need anonymous controllers just as one can dub/double any rails model for testing.

I think we all agree that this is perfectly clear. Now, how should one approach integration test with overriding one/several controllers, for instance to remove the authentication method ?

Im my case, it's actually the other way around. I want to test the auth for my API endpoints as if a client. That means not testing controllerl but also the full stack before and after it. So in my understanding: integration tests. :)

Here is the best way I came up with so far:

  • Add a test_controller to my app with overriden actions.
  • Add conditional routes if Rails.env.test?
  • Use integration/request tests on this controller.

But I hate to have test routes / controllers spilling outside my rspec folder.

@fables-tales
Copy link
Member

@muichkine the case that you've outlined (bypassing actual code, but testing authentication code) can be achieved with other RSpec tools. I can't think of a good reason for why you'd need an anonymous controller in a request spec.

@oliviermilla
Copy link
Author

@samphippen sorry then I don't know how to bypass actual code without using anonymous controllers or adding code to my app (not in the test code). Any link?

@fables-tales
Copy link
Member

@muichkine you'll want to start with https://www.relishapp.com/rspec/rspec-mocks/docs. If you're still not sure what to do, I'd suggest asking in our IRC channel or mailing list, but I don't think the solution to your problem is to add anonymous controllers to request specs.

@oliviermilla
Copy link
Author

oliviermilla commented Apr 15, 2016

Thank you @samphippen for the link. I've already hundreds of tests using rspec-mocks (it rocks :)) but I still fail to see how this would help me test requests on controllers only existing in test environment.

More explicitly:

class BaseController < ApplicationController
    class << self
         def auth_method_1
              self.class_eval do
                   # insert auth method in controller
               end
         end

         # same with auth method 2
         # same with auth method 3
    end
end

How to generate a controller in test against which I can include and test my 3 auth methods individually with requests.

I'll think about it some more. :)
Thanks.

@JonRowe
Copy link
Member

JonRowe commented Apr 18, 2016

@muichkine there is no replacement for controller tests that does what you want, I suggest you keep using controller tests, thats why the gem exists after all

@mikehale
Copy link

mikehale commented Aug 29, 2018

Something like this might work:

describe Middleware::CatchJsonParseErrors, type: :request do
  class TestController < ::ApplicationController
    def test
    end
  end

  before do
    Rails.application.routes.draw do
      post "/test", :to => "test#test"
    end
  end

  it "responds with a status code of 400 when invalid JSON is sent" do
    headers = {
      "ACCEPT" => "application/json",
      "CONTENT_TYPE" => "application/json",
    }
    body = '{'

    post "/test", params: body, headers: headers
    expect(response.status).to eq 400
  end
end

@novikserg
Copy link

novikserg commented Dec 20, 2019

thanks @mikehale , and if you also want your existing routes to persist (in case your app somehow depends on them even when testing this controller), you'll need to do this

Rails.application.routes.disable_clear_and_finalize = true # preserve original routes
Rails.application.routes.draw do
  get "/test", to: "test#test"
end

so that your routes are appended rather than fully replaced.

@mockdeep
Copy link

defining a class in that way will cause the class to become global across specs. If you need to define a custom class in your specs, then it's best to use the block format with stub_const so that it gets cleaned up:

before do
  klass = Class.new(ApplicationController) do
    ...
  end
  stub_const('TestController', klass)
end

@eliotsykes
Copy link
Contributor

eliotsykes commented May 7, 2021

Rack middleware is invoked in request specs, but not in controller specs.

Sometimes it is desirable to not couple a rack-middleware-testing request spec to a controller+route defined in app/controllers and config/routes.rb.

This technique can be useful to test rack middleware with a simple, test-only controller and route, all inside a request spec.

Here's an example that borrows from the snippets above:

# spec/requests/host_header_attack_defense_spec.rb
describe 'Host header attack defense', type: :request do
  before do
    # Define and stub TestRedirectsController so it does not leak outside this example
    klass = Class.new(ApplicationController) do
      def index
        redirect_to '/foo'
      end
    end
    stub_const('TestRedirectsController', klass)

    Rails.application.routes.disable_clear_and_finalize = true # preserve original routes
    Rails.application.routes.draw do
      get '/test_redirect', to: 'test_redirects#index'
    end
  end

  # Reset routes so they don't include test mapping from above.
  after { Rails.application.reload_routes! }

  # Mitigated by StripXForwardedHost rack middleware
  it 'prevents malicious X-Forwarded-Host redirect' do
    host! 'example.tld'
    get '/test_redirect', headers: { 'X-Forwarded-Host' => 'attacker.tld' }
    expect(response).to redirect_to 'https://example.tld/foo'
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants