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.

Community
  • 1
  • 1
Chiperific
  • 4,428
  • 3
  • 21
  • 41

4 Answers4

39

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

Benjamin Bouchet
  • 12,971
  • 2
  • 41
  • 73
  • I updated my question with my attempts at both the unit testing and feature testing strategies. Thanks for the block suggestion! – Chiperific Jul 02 '14 at 04:32
  • If you get `undefined method `allow' for RSpec` meaning you use an old version of rspec, so the `stub` syntax is to be used – Benjamin Bouchet Jul 02 '14 at 05:55
  • 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 = {})`](http://rubydoc.info/gems/rspec-mocks/frames) – Chiperific Jul 02 '14 at 15:15
  • 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? – Chiperific Jul 02 '14 at 15:18
  • https://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 debugger – Benjamin Bouchet Jul 02 '14 at 22:28
  • 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. – Chiperific Jul 03 '14 at 02:34
  • This is why I specified "be sure to choose the right version". Here is the doc for v 2.14: https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/method-stubs – Benjamin Bouchet Jul 03 '14 at 11:09
  • 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 https://github.com/yujinakayama/transpec – Benjamin Bouchet Jul 03 '14 at 11:11
  • I was looking at Rspec Core, not Rspec Mocks. *Idiot*. Thanks. – Chiperific Jul 03 '14 at 12:42
  • 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? – Benjamin Bouchet Jul 04 '14 at 02:27
  • 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`. – Chiperific Jul 04 '14 at 02:32
  • 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) – Benjamin Bouchet Jul 04 '14 at 02:41
  • This answer works for me. You left out a closing } at the end of your `before` block, though. – sixty4bit Jan 20 '15 at 22:52
10

Also works

user = create(:user) #FactoryBot

allow(controller).to receive(:current_user).and_return(user)
Chiperific
  • 4,428
  • 3
  • 21
  • 41
user2322409
  • 814
  • 9
  • 15
1

For me, worked with:

before { controller.stub!(:current_user).and_return(user) }
leompeters
  • 886
  • 1
  • 13
  • 24
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

R. Sierra
  • 1,076
  • 6
  • 19