67

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
zishe
  • 10,665
  • 12
  • 64
  • 103
Matt Fordham
  • 3,147
  • 10
  • 34
  • 51
  • 13
    Is `current_user` a class method? I guess no. So instead of `ApplicationController.stub(:current_user)` you may need `ApplicationController.any_instance.stub(:current_user)`. – skalee Jun 20 '12 at 14:02

6 Answers6

64

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)

fess .
  • 1,489
  • 1
  • 15
  • 13
  • 31
    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)` – Arctodus Jul 04 '14 at 08:06
  • 1
    Love this quote - "Using this feature is often a design smell" - from the [Rspec docs](https://github.com/rspec/rspec-mocks). – brntsllvn Mar 01 '16 at 04:40
15

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
jefflunt
  • 33,527
  • 7
  • 88
  • 126
  • 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 :) – Matt Fordham Sep 16 '11 at 19:02
  • 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 Sep 16 '11 at 19:05
  • 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 Sep 16 '11 at 19:06
  • 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])` - http://rspec.info/documentation/mocks/ – jefflunt Sep 16 '11 at 19:09
  • 1
    I tried those both (see the code I added above). Still no luck. The original `current_user` method still gets called. – Matt Fordham Sep 16 '11 at 19:17
  • 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 ...` – jefflunt Sep 16 '11 at 19:20
  • Dang. Still not working :( I edited the code above to reflect your suggestion. I really appreciate your help :) – Matt Fordham Sep 16 '11 at 19:28
  • I am having the exact same problem in request specs and cannot figure it out!! – Joe Sak Nov 16 '11 at 19:58
  • 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 Nov 16 '11 at 20:28
  • 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 – Joe Sak Nov 16 '11 at 22:06
  • 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. – bjnord Mar 23 '12 at 01:19
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}
iHiD
  • 2,450
  • 20
  • 32
3

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)
Sbbs
  • 1,610
  • 3
  • 22
  • 34
2

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.

Michael Johnston
  • 5,298
  • 1
  • 29
  • 37
  • 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! – eggmatters Apr 03 '15 at 22:57
1

None of the provided responses worked for me. As in @matt-fordam's original post, I have a request spec, not a controller spec. The test just renders the view without launching a controller.

I resolved this by stubbing the method on the view as described in this other SO post

view.stub(:current_user).and_return(etc)
Community
  • 1
  • 1
jwadsack
  • 5,708
  • 2
  • 40
  • 50