chhhris
chhhris

Reputation: 375

Controller specs throwing up undefined method

I'm really struggling with Rspec DSL! I read a lot on SO and across the internet, so I'm posting my particular issue because my feeble mind can't get to the solution.

Have a post method in my controller that updates a user's email. Works fine, but I'm struggling with the spec because all I'm getting are undefined methods for NilClass (even though I've tried stubbing every object and method, etc).

users_controller.rb

def update_user_email
   @user = User.find_by_id(params[:id])
   new_email = params[:user][:new_email].downcase.strip
   user_check = User.find_by_email('new_email')
   if user_check.blank?
     @user.email = new_email
     @user.save
     flash[:notice] = "Email updated to #{new_email}"
   else
     flash[:alert] = "This email is already being used by someone else!"
   end

   respond_with @user do |format|
     format.html { redirect_to admin_user_path(@user) }
     format.json { head :no_content }
   end
 end

Here's the spec I'm trying to write. What test should I be writing, if not this, and what can I do to prevent undefined method on NilClass errors!

users_controller_spec.rb

describe Admin::UsersController do
  let!(:user) { FactoryGirl.create(:user, password: 'oldpass', email: '[email protected]') }
  ...
  describe "admin actions for each user" do
    it "resets user email" do
      post :update_user_email, {user: {new_email: '[email protected]'} }
      response.status.should == 200
    end
  end
 ...
end

And the error:

Admin::UsersController admin actions for each user resets user email
Failure/Error: post :update_user_email, {user: {new_email: '[email protected]'} }
NoMethodError:
   undefined method `email=' for nil:NilClass

Upvotes: 0

Views: 573

Answers (3)

tomciopp
tomciopp

Reputation: 2742

The line that is failing is:

@user = User.find_by_id(params[:id)

Since you are not passing the id in during your test the user is not being found, and therefore you are trying to call email= on nil. Here is how you can clean up your controller and test.

class YourController < ApplicationController
  before_filter :find_user, only: [:update_user_email]

  def update_user_email
    new_email = params[:user][:new_email].downcase.strip
    user_check = User.where(email: new_email)
    if user_check.blank?
       @user.email = new_email
       @user.save
       flash[:notice] = "Email updated to #{new_email}"
    else
       flash[:alert] = "This email is already being used by someone else!"
    end

    respond_with @user do |format|
      format.html { redirect_to admin_user_path(@user) }
      format.json { head :no_content }
    end
  end

  def find_user
    @user = User.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      flash[:error] = "It looks like that user does not exist"
      # redirect or render
  end
end


# your test


describe "admin actions for each user" do
  it "resets user email" do
    post :update_user_email, id: user.id, user: {new_email: '[email protected]'}
    response.status.should == 200
  end
end

You may also want to consider moving the logic out of the controller and into a service object. That controller method is getting a little long.

Upvotes: 1

theTRON
theTRON

Reputation: 9649

The issue is that you also need to pass the id of the User you're wanting to update. The line which is failing is @user.email = new_email, since @user is nil.

To get your test to pass now, you'll need to change your post method to:

post :update_user_email, {id:'[email protected]', user: {new_email: '[email protected]'} }

As an aside, it is possible to say that it may be better for you to actually be doing this in the UsersController#update method, in order to maintain RESTful routes. And as for enforcing unique email address - it might be better to do this in the User class with validations.

Upvotes: 1

SteveTurczyn
SteveTurczyn

Reputation: 36860

In your post :update_user_email you're not passing :id... so @user = User.find_by_id... is not finding a user so @user is a nil object.

post :update_user_email, id: user.id, {user: {new_email: '[email protected]'} }

Upvotes: 0

Related Questions