RSpec: how to stub inherited method current_user (w/o Devise)?
Asked Answered
D

5

28

I have a controller based on MHartl's RoR4 Tutorial

And just like MHartl, I'm not using Devise, I rolled my own authentication system

Having trouble with the RSpec for UsersController#Edit since the view has a call to current_user.admin? and the controller calls @path_switch = path_switch

I keep getting RSpec errors along the lines of:

1) User Pages Edit 
 Failure/Error: visit edit_user_path(user)
 NoMethodError:
   undefined method `admin?' for nil:NilClass
 # ./app/controllers/users_controller.rb:106:in `path_switch'
 # ./app/controllers/users_controller.rb:53:in `edit'
 # ./spec/requests/user_pages_spec.rb:54:in `block (3 levels) in <top (required)>'

UsersController:

class UsersController < ApplicationController
  ...
  def edit
    @user = User.find(params[:id])
    @path_switch ||= path_switch                        #error
  end
  ...
  def path_switch
    if current_user.admin?                               #error
      users_path
    else
      root_path
    end
  end
end

I found this really helpful article that gives me hope that I'm on the right track, but I can't get it to work.

Here's as far as I've gotten (updated):

user_pages_spec.rb:

require 'spec_helper'
require 'support/utilities'

describe "User Pages" do
  #include SessionsHelper

  let(:user) { FactoryGirl.create(:user) }
  let(:current_user) {user}

  subject { page }

  describe "Edit" do
    before do
      sign_in(user)
      visit edit_user_path(user) 
    end

    it '(check links and content)' do
      should have_button('Submit')
      should have_link('Cancel')
      should have_content(user.fname+"\'s profile")
    end
   ...
  end
...
end

But current_user is still coming back nil

Any help/guidance is appreciated. Thanks!


Adding include SessionsHelper to the top describe block of my user_pages_edit.rb seems to try and use the sign_in(path) from that helper. Creating an issue between RSpec and cookies.permanent. So that's a bust.

unfortunately, this brings me right back to my .admin? error.

There are two calls to current_user.admin?

One is in the controller:

  def path_switch
    if current_user.admin?    #error current_user == nil
      users_path
    else
      root_path
    end
  end

One is in the view as ERB:

<% if current_user.admin? %>
  <div class="row  col-xs-6 col-sm-6 col-md-3">
    <div class="input-group input-selector">
    ...

All I need to do is figure out how to set current_user.admin = true and pass it to the controller (and then hopefully the view) so that the page can load. To do that, all I need to do is set current_user = user because user.admin == true.

Dyer answered 2/7, 2014 at 3:13 Comment(0)
T
10

Also works

user = create(:user) #FactoryBot

allow(controller).to receive(:current_user).and_return(user)
Tommyetommyrot answered 29/4, 2016 at 13:57 Comment(3)
This is still working for Rspec 3.7 and Rails 5.1. I just check herePeres
This is the simplest way. Don't use Devise helpers unless you absolutely need to. Nothing but a headache.Macropterous
Note, if you're in a Helper spec, you need to use allow(helper) instead of allow(controller). Otherwise, the same and it works great. Thanks.Eugenie
F
40

If you are doing unit testing of your controller you can simply stub your current_user in a before block, like this:

let(:user) { ... }

# RSpec version <= 2 syntax:
before { controller.stub(:current_user) { user } }

# RSpec version >= 3 syntax:
before { allow(controller).to receive(:current_user) { user } }

If you are doing feature or request testing, I advise you to perform a real log-in by creating a user in your database, then passing through your log-in page with this user credentials


Here you seem to be doing a feature test, you should write a helper that perform the creation of the user record and go through the log-in.

Additionally in feature testing to gain a lot of time while running your test, do not hesitate to group your assertions in the same block. Clearly, instead of:

it { should have_button('Submit')}
it { should have_link('Cancel')}
it { should have_content(user.fname+"\'s profile")}

You can write

it 'check links and content' do
  should have_button('Submit')
  should have_link('Cancel')
  should have_content(user.fname+"\'s profile")
end

That will avoid to generate several session of your feature environment, and also to log-in several times

Fraktur answered 2/7, 2014 at 3:57 Comment(13)
I updated my question with my attempts at both the unit testing and feature testing strategies. Thanks for the block suggestion!Dyer
If you get undefined method allow' for RSpec` meaning you use an old version of rspec, so the stub syntax is to be usedFraktur
so would allow(controller).to receive(:current_user) { user } become controller.stub(:current_user) { user }? I'm struggling to find api docs for this. I did find this: stub_const(constant_name, value, options = {})Dyer
I agree with your comment that this is a feature test and would really like to get that method working. Any idea why none of my files call to .empty? but the RSpec error says that's the problem? Is it erroring in the core somewhere? How do I find out what nil object is getting the .empty? call?Dyer
relishapp.com/rspec for rspec doc, be sure to chose your right version, As for finding the source of your error message, if you cannot find it by reading your backtrace then you must use a debuggerFraktur
the relishapp didn't help (can't search for stub and couldn't find it in the links) and I just spent way too long trying to resolve gem dependencies after upgrading my RSpec to handle allow, so I rolled back. I need help with what the stub syntax should look like.Dyer
This is why I specified "be sure to choose the right version". Here is the doc for v 2.14: relishapp.com/rspec/rspec-mocks/v/2-14/docs/method-stubsFraktur
Also if you decide to migrate to 3.0 do not migrate manually, it takes ages. You have this nice tool that cover 98% of it automatically github.com/yujinakayama/transpecFraktur
I was looking at Rspec Core, not Rspec Mocks. Idiot. Thanks.Dyer
Just read your edit. Understand this: In feature spec (i.e when using capybara) you must not stub stuff. To make it clear, imagine that your rspec is controlling your browser, and you have no control over your server, like a real life situation (this is not entirely true off course, but it help understand and separate things). Bottom line is: you must not stub current_user. Anyhow I start to wonder if the problem comes from your specs only or from your implementation. Does it work when you manually log-in and access this page? Or do you have the same error message?Fraktur
Views through localhost work fine. I've simulated the test and all pages load, all routes work, all forms submit. Trace shows hang up is my current_user method, which is defined in sessions_helper.Dyer
Try removing all your stub :current_user and add to the top of your example (just bellow the first describe) include SessionsHelper (I doubt it will work, but we never know, anyhow this bug is weird)Fraktur
This answer works for me. You left out a closing } at the end of your before block, though.Temperate
T
10

Also works

user = create(:user) #FactoryBot

allow(controller).to receive(:current_user).and_return(user)
Tommyetommyrot answered 29/4, 2016 at 13:57 Comment(3)
This is still working for Rspec 3.7 and Rails 5.1. I just check herePeres
This is the simplest way. Don't use Devise helpers unless you absolutely need to. Nothing but a headache.Macropterous
Note, if you're in a Helper spec, you need to use allow(helper) instead of allow(controller). Otherwise, the same and it works great. Thanks.Eugenie
C
1

For me, worked with:

before { controller.stub!(:current_user).and_return(user) }
Craniate answered 11/6, 2015 at 20:9 Comment(0)
C
1

I run into the same problem with a legacy Rails 4 app and based my solution on this test case of Rspec Views.

First define a helper that will define the missing helper method in the controller instance

# /spec/support/concerns/view_helper.rb
module ViewHelper
  def include_current_user_helper(&block)
    controller.singleton_class.class_eval do
      define_method :current_user, &block
      helper_method :current_user
    end
  end
end

Then configure Rspec to include it in all the view helpers

# spec/rails_helper.rb
# ...
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

RSpec.configure do |config|
  # ...
  config.include ViewHelper, type: :view
end

And in the view specs it is called like this

RSpec.describe 'something' do
  let!(:user) { FactoryGirl.create(:user) } # Note the "!" there
  before { include_current_user_helper { user } }

  # do stuff
end

Note: the call to let with bang is important as content inside the block will be executed lazily, outside of the test scope, and user will be nill if not

Cassette answered 28/9, 2019 at 0:21 Comment(0)
N
0

Why not mock the can? mehtod?

i ran into the same issue because if can? fails, it shows the error undefined mehtod: current_user

fixed it by

allow(view).to receive(:can?).with(:show, customer).and_return(true)
Nahum answered 6/4, 2024 at 15:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.