Testing a scoped find in a Rails controller with RSpec - ruby-on-rails

I've got a controller called SolutionsController whose index action is different depending on the value of params[:user_id]. If its nil, then it simply displays all of the solutions on my site, but if its not nil, then it displays all of the solutions for the given user id.
Here it is:
def index
if(params[:user_id])
#solutions = #user.solutions.find(:all)
else
#solutions = Solution.find(:all)
end
end
and #user is determined like this:
private
def load_user
if(params[:user_id ])
#user = User.find(params[:user_id])
end
end
I've got an Rspec test to test the index action if the user is nil:
describe "GET index" do
context "when user_id is nil" do
it "should find all of the solutions" do
Solution.should_receive(:find).with(:all).and_return(#solutions)
get :index
end
end
end
however, can someone tell me how I write a similar test for the other half of my controller, when the user id isn't nil?
Something like:
describe "GET index" do
context "when user_id isn't nil" do
before(:each) do
#user = Factory.create(:user)
#solutions = 7.times{Factory.build(:solution, :user => #user)}
#user.stub!(:solutions).and_return(#solutions)
end
it "should find all of the solutions owned by a user" do
#user.should_receive(:solutions).and_return(#solutions)
get :index, :user_id => #user.id
end
end
end
But that doesn't work. Can someone help me out?

try and stub out the find on the User for when the user_id is not nil
describe "GET index" do
context "when user_id isn't nil" do
before(:each) do
#user = Factory.create(:user)
#solutions = 7.times{Factory.build(:solution, :user => #user)}
#user.stub!(:solutions).and_return(#solutions)
User.stub(:find).and_return(#user)
end
it "should find all of the solutions owned by a user" do
#user.should_receive(:solutions).and_return(#solutions)
get :index, :user_id => #user.id
end
end
end

Related

rspec for the controller without factory girl

I am trying to write spec code for my controller it gets failed. And i am not sure where it gets failed.
Controller Code
def index
#users = User.all
end
def update
authorize! :update, #user
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_index_path }
else
format.html { render :index }
end
end
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.permit(:active)
end
Spec Code for the above controller
RSpec.describe UserController, type: :controller do
describe 'GET #index' do
let(:user) {User.create!(name: "hari")}
context 'with user details'do
it 'loads correct user details' do
get :index
expect(response).to permit(:user)
end
end
context 'without user details' do
it 'doesnot loads correct user details' do
get :index
expect(response).not_to permit(:user)
end
end
end
describe 'Patch #update' do
context 'when valid params' do
let(:attr) do
{active: 'true'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'redirects to user_index_path ' do
expect(response).redirect_to(user_index_path)
end
it 'sets active state' do
expect(#user.active?('true')).to be true
end
end
context 'when invalid param' do
let(:attr) do
{active: 'nil'}
end
before(:each) do
#user = subject.current_user
put :update, params: { user: attr }
#user.reload
end
it 'render index' do
expect(respone.status).to eq(200)
end
it 'doesnot change active state' do
expect(#user.active?(nil)).to be true
end
end
end
end
I am just a beginner and tried the spec code for my controller by checking https://relishapp.com/rspec/rspec-rails/docs/gettingstarted. Can you help me where my spec goes wrong or could anyone give me a few test examples for these methods or could redirect me to an rspec guide? the index method is getting failed
and my
terminal log is
1) UserController GET #index with user details loads correct user details
Failure/Error: expect(response).to permit(:user)
NoMethodError:
undefined method `permit' for #<RSpec::ExampleGroups::UserController::GETIndex::WithUserDetails:0x00005614152406b0>
Did you mean? print
# ./spec/controllers/user_controller_spec.rb:10:in `block (4 levels) in <top (required)>'

How can I test update_attributes function in Rails with RSpec?

In my Rails 4 app I have this update action:
class UsersController < ApplicationController
...
def update
current_email = #user.email
new_email = user_params[:email].downcase
if #user.update_attributes(user_params)
if current_email != new_email
#user.email = current_email
#user.new_email = new_email.downcase
#user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
redirect_to edit_user_path(#user)
else
render :edit
end
end
...
end
It basically makes sure that a user cannot simply save any new email address. He will have to confirm it first by clicking on a link in an email we send to him.
This works great, however, for some reason I haven't found a way to test it.
The following RSpec test keeps failing no matter what I do:
it "changes the user's new_email attribute" do
#user = FactoryGirl.create(:user, :email => "john#doe.com")
patch :update, :id => #user, :user => FactoryGirl.attributes_for(:user, :email => "new#email.com")
expect(#user.reload.new_email).to eq("new#email.com")
end
#user.new_email is always nil and the test always fails. What am I missing here?
Re-factoring my update action wouldn't be a problem at all. Maybe there's a better way? Thanks for any help.
I would write the spec like so:
let(:user) { FactoryGirl.create(:user, email: "john#doe.com") }
it "changes the user's new_email attribute" do
expect do
patch :update, id: #user, user: FactoryGirl.attributes_for(:user, email: "new#email.com")
user.reload
end.to change(user, :new_email).from("john#doe.com").to("new#email.com")
end
When it comes to the controller action itself the problem is that the new_email property is never saved to the database, besides that its kind of a mess. You can clean it up by using ActiveRecord::Dirty which tracks attribute changes in the model:
class User < ApplicationRecord
# updates user with attrs but moves a new email to the `new_email`
# column instead
def update_with_email(attrs, &block)
update(attrs) do |record|
if record.email_changed?
record.new_email = record.email.downcase
record.restore_attribute!(:email)
end
# keeps the method signature the same as the normal update
yield record if block_given?
end
end
end
Putting this business logic in the model also lets you test it separatly:
RSpec.describe User, type: :model do
describe "#update_with_email" do
let(:user) { FactoryGirl.create(:user) }
it "does not change the email attribute" do
expect do
user.update_with_email(email: ”xxx#example.com”)
user.reload
end.to_not change(user, :email)
end
it "updates the new_email" do
expect do
user.update_with_email(email: ”xxx#example.com”)
user.reload
end.to change(user, :new_email).to('xxx#example.com')
end
end
end
This lets you keep the controller nice and skinny:
def update
if #user.update_with_email(user_params)
if #user.new_email_changed?
#user.send_email_confirmation_email
flash[:success] = "Please click the link we've just sent you to confirm your new email address."
else
flash[:success] = "User updated."
end
# You probably want to redirect the user away from the form instead.
redirect_to edit_user_path(#user)
else
render :edit
end
end

RSpec controller: redirect_to other controller

I have this issue with test my CommentsController:
Failure/Error: redirect_to user_path(#comment.user), notice: 'Your
comment was successfully added!' ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"users", :id=>nil}
missing required keys: [:id]
This is my method in my controller:
def create
if params[:parent_id].to_i > 0
parent = Comment.find_by_id(params[:comment].delete(:parent_id))
#comment = parent.children.build(comment_params)
else
#comment = Comment.new(comment_params)
end
#comment.author_id = current_user.id
if #comment.save
redirect_to user_path(#comment.user), notice: 'Your comment was successfully added!'
else
redirect_to user_path(#comment.user), notice: #comment.errors.full_messages.join
end
end
This is my RSpec:
context "User logged in" do
before :each do
#user = create(:user)
sign_in #user
end
let(:comment) { create(:comment, user: #user, author_id: #user.id) }
let(:comment_child) { create(:comment_child, user: #user, author_id: #user.id, parent_id: comment.id) }
describe "POST #create" do
context "with valid attributes" do
it "saves the new comment object" do
expect{ post :create, comment: attributes_for(:comment), id: #user.id}.to change(Comment, :count).by(1)
end
it "redirect to :show view " do
post :create, comment: attributes_for(:comment), user: #user
expect(response).to redirect_to user_path(comment.user)
end
end
...
end
end
My Comment model:
class Comment < ActiveRecord::Base
belongs_to :user
acts_as_tree order: 'created_at DESC'
VALID_REGEX = /\A^[\w \.\-#:),.!?"']*$\Z/
validates :body, presence: true, length: { in: 2..240}, format: { with: VALID_REGEX }
end
How Can I add user_id to that request? When I change code in my controller redirect_to user_path(#comment.user) to redirect_to user_path(current_user) - test pass. May I redirect_to user in comments controller? Is any posibility to do it right? Thanks for your time.
Basically the error is caused by the fact that the #comment.user is nil.
Lets start fixing it by cleaning up the spec:
context "User logged in" do
# declare lets first.
let(:user) { create(:user) }
let(:comment) { create(:comment, user: user, author: user) }
# use do instead of braces when it does not fit on one line.
let(:comment_child) do
# use `user: user` instead of `user_id: user.id`.
# the latter defeats the whole purpose of the abstraction.
create(:comment_child, user: user, author: user, parent: comment)
end
before { sign_in(user) }
describe "POST #create" do
context "with valid attributes" do
it "saves the new comment object" do
expect do
post :create, comment: attributes_for(:comment)
end.to change(Comment, :count).by(1)
end
it "redirects to the user" do
post :create, comment: attributes_for(:comment)
expect(response).to redirect_to user
end
end
end
end
You should generally avoid using instance vars and instead use lets in most cases. Using a mix just adds to the confusion since its hard to see what is lazy loaded or even instantiated where.
Then we can take care of the implementation:
def create
#comment = current_user.comments.new(comment_params)
if #comment.save
redirect_to #comment.user, notice: 'Your comment was successfully added!'
else
# ...
end
end
private
def comment_params
# note that we don't permit the user_id to be mass assigned
params.require(:comment).permit(:foo, :bar, :parent_id)
end
Basically you can cut a lot of the overcomplication:
Raise an error if there is no authenticated user. With Devise you would do before_action :authenticate_user!.
Get the user from the session - not the params. Your not going to want or need users to comment on the behalf of others.
Wrap params in the comments key.
Use redirect_to #some_model_instance and let rails do its polymorpic routing magic.
Let ActiveRecord throw an error if the user tries to pass a bad parent_id.
Also does your Comment model really need both a user and author relationship? Surely one of them will suffice.

RSPEC test index action with before_action filter

I have a before_action filter and want to test that the index action is only executed if the user is logged in. Simply put, i don't know how to do this. I'm using my own simple authentication and i know i could use CanCan or similar but for my own learning i'm doing it the hard way!
ApplicationController.rb
helper_method :logged_in
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:current_user]) if session[:current_user]
end
def logged_in
unless current_user
redirect_to root_path
end
end
ActivitiesController.rb
before_action :logged_in
def index
#activities = Activity.all.where(user_id: #current_user)
end
Activities_Controller_spec.rb
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
session[:current_user] = #activity.user_id
#current_user = User.find_by_id(session[:current_user]) if session[:current_user]
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to user_activities_path
end
end
end
activities.rb(Factory)
FactoryGirl.define do
factory :activity do
association :user
title { Faker::App.name }
activity_begin { Faker::Date.forward(10) }
activity_end { Faker::Date.forward(24) }
end
end
I'm getting the following error:
Failure/Error: expect(response).to redirect_to user_activities_path
Expected response to be a redirect to <http://test.host/users/1/activities> but was a redirect to <http://test.host/>.
Expected "http://test.host/users/1/activities" to be === "http://test.host/".
After long discussion I think tests should be smth like this (it is not tested :) )
require 'rails_helper'
RSpec.describe ActivitiesController, :type => :controller do
describe "GET index" do
before(:each) do
#activity = FactoryGirl.create(:activity)
end
context 'when user is logged' do
before(:each) do
session[:current_user] = #activity.user_id
end
it "shows all activities for signed in user" do
get :index, {user_id: #activity.user_id}
expect(response).to be_success
end
end
context 'when user is anonymous' do
it "redirects user to root path" do
get :index, {user_id: #activity.user_id}
expect(response).to redirect_to root_path
end
end
end
end

Stubbing a before_filter with RSpec

I'm having trouble understanding why I can't seem to stub this controller method :load_user, since all of my tests fail if I change the actual implementation of :load_user to not return and instance of #user.
Can anybody see why my stub (controller.stub!(:load_user).and_return(#user)) seems to fail to actually get called when RSpec makes a request to the controller?
require 'spec_helper'
describe TasksController do
before(:each) do
#user = Factory(:user)
sign_in #user
#task = Factory(:task)
User.stub_chain(:where, :first).and_return(#user)
controller.stub!(:load_user).and_return(#user)
end
#GET Index
describe "GET Index" do
before(:each) do
#tasks = 7.times{Factory(:task, :user => #user)}
#user.stub!(:tasks).and_return(#tasks)
end
it "should should find all of the tasks owned by a user" do
#user.should_receive(:tasks).and_return(#tasks)
get :index, :user_id => #user.id
end
it "should assign all of the user's tasks to the view" do
get :index, :user_id => #user.id
assigns[:tasks].should be(#tasks)
end
end
#GET New
describe "GET New" do
before(:each) do
#user.stub_chain(:tasks, :new).and_return(#task)
end
it "should return a new Task" do
#user.tasks.should_receive(:new).and_return(#task)
get :new, :user_id => #user.id
end
end
#POST Create
describe "POST Create" do
before(:each) do
#user.stub_chain(:tasks, :new).and_return(#task)
end
it "should create a new task" do
#user.tasks.should_receive(:new).and_return(#task)
post :create, :user_id => #user.id, :task => #task.to_s
end
it "saves the task" do
#task.should_receive(:save)
post :create, :user_id => #user.id, :task => #task
end
context "when the task is saved successfully" do
before(:each) do
#task.stub!(:save).and_return(true)
end
it "should set the flash[:notice] message to 'Task Added Successfully'"do
post :create, :user_id => #user.id, :task => #task
flash[:notice].should == "Task Added Successfully!"
end
it "should redirect to the user's task page" do
post :create, :user_id => #user.id, :task => #task
response.should redirect_to(user_tasks_path(#user.id))
end
end
context "when the task isn't saved successfully" do
before(:each) do
#task.stub(:save).and_return(false)
end
it "should return to the 'Create New Task' page do" do
post :create, :user_id => #user.id, :task => #task
response.should render_template('new')
end
end
end
it "should attempt to authenticate and load the user who owns the tasks" do
context "when the tasks belong to the currently logged in user" do
it "should set the user instance variable to the currently logged in user" do
pending
end
end
context "when the tasks belong to another user" do
it "should set the flash[:notice] to 'Sorry but you can't view other people's tasks.'" do
pending
end
it "should redirect to the home page" do
pending
end
end
end
end
class TasksController < ApplicationController
before_filter :load_user
def index
#tasks = #user.tasks
end
def new
#task = #user.tasks.new
end
def create
#task = #user.tasks.new
if #task.save
flash[:notice] = "Task Added Successfully!"
redirect_to user_tasks_path(#user.id)
else
render :action => 'new'
end
end
private
def load_user
if current_user.id == params[:user_id].to_i
#user = User.where(:id => params[:user_id]).first
else
flash[:notice] = "Sorry but you can't view other people's tasks."
redirect_to root_path
end
end
end
Can anybody see why my stub doesn't work? Like I said, my tests only pass if I make sure that load_user works, if not, all my tests fail which makes my think that RSpec isn't using the stub I created.
Stubbing out load_user breaks your tests because stubbing the method neuters it. When the controller calls load_user, it is no longer running your original code. It's now just returning whatever you specify in and_return(...) (which is getting returned to the ActionController callback stack, which ignores anything other than false).
Your controller code isn't using the return value of that method; it's using the variable instantiated within it. Since the original code for the load_user method isn't being run, the #user instance variable is never instantiated. (The #user variable in your tests is only visible to your tests.)
But with all the other stubs you have, I don't see any reason why you should need to stub out load_user at all. As long as you're stubbing current_user to return #user (which I assume is being done in the sign_in method), then there shouldn't be any need.
you can also try to verify that the stub works by doing an assertion like
controller.current_user.should == #user

Resources