How to stub ApplicationController method in request spec

39,930

Solution 1

skalee seems to have provided the correct answer in the comment.

If the method you're trying to stub is an instance method (most likely) and not a class method then you need use:

ApplicationController.any_instance.stub(:current_user)

Solution 2

Here are a couple of examples of the basic form.

controller.stub(:action_name).and_raise([some error])
controller.stub(:action_name).and_return([some value])

In your particular case, I believe the proper form would be:

controller.stub(:current_user).and_return([your user object/id])

Here's a full working example from a project I work on:

describe PortalsController do

  it "if an ActionController::InvalidAuthenticityToken is raised the user should be redirected to login" do
    controller.stub(:index).and_raise(ActionController::InvalidAuthenticityToken)
    get :index
    flash[:notice].should eql("Your session has expired.")
    response.should redirect_to(portals_path)
  end

end

To explain my full example, basically what this does is verify that, when an ActionController::InvalidAuthenticityToken error is raised anywhere in the app, that a flash message appears, and the user is redirected to the portals_controller#index action. You can use these forms to stub out and return specific values, test an instance of a given error being raised, etc. There are several .stub(:action_name).and_[do_something_interesting]() methods available to you.


Update (after you added your code): per my comment, change your code so it reads:

require 'spec_helper'

describe "Login" do

   before(:each) do
      @mock_controller = mock("ApplicationController") 
      @mock_controller.stub(:current_user).and_return(User.first)
   end

  it "logs in" do
    visit '/'
    page.should have_content("Hey there user!")
  end

end

Solution 3

This works for me and gives me a @current_user variable to use in tests.

I have a helper that looks like this:

def bypass_authentication
  current_user = FactoryGirl.create(:user)

  ApplicationController.send(:alias_method, :old_current_user, :current_user)
  ApplicationController.send(:define_method, :current_user) do 
    current_user
  end
  @current_user = current_user
end

def restore_authentication
  ApplicationController.send(:alias_method, :current_user, :old_current_user)
end

And then in my request specs, I call:

before(:each){bypass_authentication}
after(:each){restore_authentication}

Solution 4

For anyone else who happens to need to stub an application controller method that sets an ivar (and was stymied by endless wanking about why you shouldn't do that) here's a way that works, with the flavour of Rspec circa October 2013.

before(:each) do
  campaign = Campaign.create!
  ApplicationController.any_instance.stub(:load_campaign_singleton)
  controller.instance_eval{@campaign = campaign}
  @campaign = campaign
end

it stubs the method to do nothing, and sets the ivar on rspec's controller instance, and makes it available to the test as @campaign.

Solution 5

For Rspec 3+ the new api is:

For a controller test, nice and short:

allow(controller).to receive(:current_user).and_return(@user)

Or for all instances of ApplicationController:

allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(@user)
Share:
39,930
Matt Fordham
Author by

Matt Fordham

Updated on July 05, 2022

Comments

  • Matt Fordham
    Matt Fordham almost 2 years

    I am needing to stub the response of a current_user method in an Rspec/capybara request spec. The method is defined in ApplicationController and is using helper_method. The method should simply return a user id. Within the test, I'd like this method to return the same user id each time.

    Alternatively, I could fix my problem by setting session[:user_id] in the spec (which is what current_user returns)... but that doesn't seem to work either.

    Are either of these possible?

    Edit:

    Here is what I've got (it is not working. It just runs the normal current_user method).

    require 'spec_helper'
    
    describe "Login" do
    
       before(:each) do
         ApplicationController.stub(:current_user).and_return(User.first)
       end
    
      it "logs in" do
        visit '/'
        page.should have_content("Hey there user!")
      end
    
    end
    

    Also not working:

    require 'spec_helper'
    
    describe "Login" do
    
      before(:each) do
        @mock_controller = mock("ApplicationController") 
        @mock_controller.stub(:current_user).and_return(User.first)
      end
    
      it "logs in" do
        visit '/'
        page.should have_content("Hey there user!")
      end
    
    end
    
  • Matt Fordham
    Matt Fordham over 12 years
    Thanks for your response, but that looks like a controller spec, as opposed to a request spec. I am pretty new to testing, so I could be wrong :)
  • jefflunt
    jefflunt over 12 years
    Stub just acts as a replacer for any method defined on any class. In the example you gave, it is a method current_user defined on ApplicationController, correct? The same basic form should work, unless I'm misunderstanding your question. Can you post the best form of the test you've written, even if it's not working?
  • jefflunt
    jefflunt over 12 years
    So, I was assuming that your test was inside a spec that starts with describe ApplicationController, so my bad there. If that's not the case, then you should still be able to do ApplicationController.stub(:current_user).and_return([your user object/id])
  • jefflunt
    jefflunt over 12 years
    Or maybe declare a mock object first for the ApplicationController, and then define the stub on that: mock_controller = mock("ApplicationController") followed by mock_controller.stub(:current_user).and_return([your user object/id]) - rspec.info/documentation/mocks
  • Matt Fordham
    Matt Fordham over 12 years
    I tried those both (see the code I added above). Still no luck. The original current_user method still gets called.
  • jefflunt
    jefflunt over 12 years
    The code you posted declares mock_controller as a local variable (which is only accessible from within the before method). Change the declaration to @mock_controller ...
  • Matt Fordham
    Matt Fordham over 12 years
    Dang. Still not working :( I edited the code above to reflect your suggestion. I really appreciate your help :)
  • Joe Sak
    Joe Sak over 12 years
    I am having the exact same problem in request specs and cannot figure it out!!
  • Joe Sak
    Joe Sak over 12 years
    Are you wrapping your def current_user code in if cookies[:auth_token] or something similar, by chance? Because that's what mine was doing so it still returned nil(false), so I stubbed User.find_by_auth_token! and returned my mock user, removed the if cookies[:auth_token] line, and now I'm good. It should still come back nil if there is no auth_token in the visitors' cookie
  • Joe Sak
    Joe Sak over 12 years
    Bleh: you know what? That doesn't work when cookies[:auth_token] is not present since User.find_by_auth_token! raises an error for user not found... I wish we could just stub :current_user!! :P
  • bjnord
    bjnord about 12 years
    I'm trying to do something even more basic in a request spec: WidgetController.stub(:index) followed by get :index. My actual WidgetController index method gets called every time; it's like the stub isn't there. I tried both forms listed in this answer.
  • Arctodus
    Arctodus almost 10 years
    This still works for Rspec 3 but gives deprecation warning. This is the new syntax: allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(your_test_user)
  • eggmatters
    eggmatters about 9 years
    Tool late to end the endless wanking but just in the nick of time to keep me from throwing my computer out the window. Thanks!
  • brntsllvn
    brntsllvn about 8 years
    Love this quote - "Using this feature is often a design smell" - from the Rspec docs.