I have following spec to test controller method:
context "#create" do
it "should redirect when model is valid" do
User.stub!(:valid?).and_return(true)
post :create, :user => FactoryGirl.attributes_for(:user)
response.should redirect_to("/")
end
it "should render new template when model is invalid" do
User.stub!(:valid?).and_return(false)
post :create, :user => FactoryGirl.attributes_for(:user)
response.should render_template(:new)
end
end
And controller itself:
def create
#user = User.new(params[:user])
if #user.save
redirect_to "/", :notice => "User created"
else
render "new"
end
end
Pretty much straightforward code, but somehow stub! method just doesn't really stubs, so second spec fails with expecting <"new"> but rendering with <"">. It just redirects like if valid? returned true.
I'm quite new to Rails world. What am I missing? Thanks.
When saving an object, Rails calls valid? on an instance of a class. But you've stubbed valid? on the class itself. That won't work.
What you want to do here is stub save on the instance of User that is being saved, e.g.
User.stub(:new) { mock_model(User, :save => true) }
#user = User.new # #user is now a mock object
#user.save # mock object returns true
For the other example:
User.stub(:new) { mock_model(User, :save => false) }
#user = User.new # again, #user is a mock
#user.save # mock object returns false
Related
I am trying to test a controller create method in a rails app using RSpec as shown below:
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user.id)
else
render new_user_path
flash[:error] = "User not saved"
end
end
However if i stub out .new to prevent the test from using Active Record and the User model by forcing it to return true the id of the #user is not set by .save as normal so I cannot test for it redirecting to user_path(#user.id) as #user.id is nil
Here is my initial test for RSpec:
it "creates a user and redirects" do
expect_any_instance_of(User).to receive(:save).and_return(true)
post :create, { user: {name: "John", username: "Johnny98", email: "johnny98#example.com"} }
expect(assigns(:user).name).to eq("John")
expect(response).to redirect_to user_path(assigns(:user))
end
How should I test for this redirect in RSpec.
You should use mocks - https://www.relishapp.com/rspec/rspec-mocks/docs.
user = double("user", id: 1, save: true)
Then you should mock you method with double you've just created
expect(User).to receive(:new).and_return(user)
And then test redirect.
expect(response).to redirect_to user_path(user)
I hope this will help.
I would do it in this way:
it 'should redirect to a user if save returned true' do
#user_instance = double
#user_id = double
allow(User).to receive(:new).and_return(#user_instance)
allow(#user_instance).to receive(:save).and_return(true)
allow(#user_instance).to receive(:id).and_return(#user_id)
post :create, {:user => valid_attributes}
expect(response).to redirect_to(user_path(#user_id))
end
When a user im trying to unfollow doesn't exist I throw an exception that sets a flash error message and redirects the user back to the page they were on.
All the access to the twitter gem is handled by the TwitterManager class which is a regular ruby class that extend ActiveModel::Naming to enable the mock_model.
Now Im having a real hard time figuring out how this should be tested. The code below works but feels very wrong. The only way I could stub the twitter.unfollow method was with controller.send(:twitter).stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))
I tried using TwitterManager.any_instance.stub(:unfollow) but that did obviously not do what I thought it would do.
How can I make this better? What things have I totally misunderstood?
Spec
describe TwitterController do
before(:each) do
controller.stub(:twitter).and_return(mock_model("TwitterManager", unfollow: true, follow: true))
end
it "unfollows a user when given a nickname" do
#request.env['HTTP_REFERER'] = '/followers'
post 'unfollow', id: "existing_user"
response.should redirect_to followers_path
end
describe "POST 'unfollow'" do
it "does not unfollow a user that does not exist" do
controller.send(:twitter).stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))
#request.env['HTTP_REFERER'] = '/followers'
post 'unfollow', id: "non_existing_user"
flash[:error].should_not be_nil
flash[:error].should have_content("not found, could not unfollow")
response.should redirect_to followers_path
end
end
Controller
def unfollow
begin
twitter.unfollow(params[:id])
respond_to do |format|
format.html { redirect_to :back, notice: "Stopped following #{params[:id]}" }
end
rescue Twitter::Error::NotFound
redirect_to :back, :flash => { error: "User #{params[:id]} not found, could not unfollow user" }
end
end
[ more code ]
private
def twitter
twitter_service ||= TwitterFollower.new(current_user)
end
Rspec 2.8.0
Rails 3.2.0
You could clean it up a little by saving the mock TwitterManager as an instance variable in the before block and stubbing directly on that object:
describe TwitterController do
before(:each) do
#twitter = mock_model("TwitterManager", unfollow: true, follow: true)
controller.stub(:twitter).and_return(#twitter)
end
# ...
describe "POST 'unfollow'" do
it "does not unfollow a user that does not exist" do
#twitter.stub(:unfollow).and_raise(Twitter::Error::NotFound.new("", {}))
# ...
end
end
end
But I wouldn't say what you're doing is "very wrong" :-)
I am having difficulty getting a rspec test for a controller to pass. I would like to test that the POST create action works. I am using rails (3.0.3), cancan (1.4.1), devise (1.1.5), rspec (2.3.0)
The model is dead simple
class Account < ActiveRecord::Base
attr_accessible :name
end
The controller is standard as well (straight out of scaffolding)
class AccountsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
load_and_authorize_resource
...
def create
#account = Account.new(params[:account])
respond_to do |format|
if #account.save
format.html { redirect_to(#account, :notice => 'Account was successfully created.') }
format.xml { render :xml => #account, :status => :created, :location => #account }
else
format.html { render :action => "new" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
and the rspec test I would like to pass is (excuse the title, perhaps not the most appropriate one)
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
post :create, :account => {"name" => "Jimmy Johnes"}
response.should be_success
sign_out #user
end
Yet all I get is
AccountsController get index should call create on account when POST create is called
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/accounts_controller_spec.rb:46
Other actions can be tested and do pass (i.e. GET new)
here is the test for GET new
it "should allow logged in admin to call new on account controller" do
#user = Factory.create(:user)
#user.admin=true
#user.save
sign_in #user #this is an admin
get :new
response.should be_success
sign_out #user
end
and for completion here is the ability file
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
Any ideas? My guess is that I am using the wrong rspec expectation, since the code does work (it is just that the test does not perform as desired!)
response.should be_success returns true if the response code is in the range 200-299. But the create action redirects, so the response code gets set to 302, thus the failure.
You can test this by using response.should redirect_to. Check the output of the standard RSpec controller generator for an example, which might look like this:
it "redirects to the created account" do
Account.stub(:new) { mock_account(:save => true) }
post :create, :account => {}
response.should redirect_to(account_url(mock_account))
end
The rspec test that got the test to pass was (thanks to zetetic's advice):
it "should call create on account when POST create is called" do
#user = Factory.create(:user)
#user.admin = true
#user.save
sign_in #user #this is an admin
account = mock_model(Account, :attributes= => true, :save => true)
Account.stub(:new) { account }
post :create, :account => {}
response.should redirect_to(account_path(account))
sign_out #user
end
Im trying to test a condition where on successful signup a Success Template is rendered by the following controller code
def create
#user = User.new(params[:user])
if #user.save
render :template => "success"
else
flash[:notice] = "Oops Somethings not quite right! :("
render :action => "new"
end
end
I am using the following spec to test out this code
before(:each) do
#user = User.new
#user.attributes = valid_attributes
#params = valid_attributes
#user.stub!(:save).and_return(true)
end
def do_post
post :create
end
it "should create new user " do
count = User.count
do_post
user = User.new(#params)
user.save.should eql(true)
User.count.should eql(count + 1)
end
it "should render the success page on successful signup" do
do_post
#user.save
response.should render_template("success") if #user.save
end
But the example fails "it should render success page on successful signup" with this error message
1)
'UsersController handling POST /users should render the success page on successful signup' FAILED
expected "success", got "users/new.html.erb"
./spec/controllers/users_controller_spec.rb:67:
The success view is an template stored in the views/users/ without an action. Im guessing im making a very fundamental mistake and would like some help .
You are stubbing the #user variable in the test, but the controller will instantiate a new instance so the stub won't be in place.
It's not a good idea to use a stub in this case just to emulate a successful save call. Why don't you supply valid data instead and make sure the action is successful?
The following code is for RSpec > 2.1 and it uses the expect syntax.
before(:each) do
#params = valid_attributes
end
it "should create new user" do
#_before = User.count
post :create, :user => #params
expect(assigns(:user)).to_not be_new_record
expect(User.count).to eq(#_before + 1)
end
it "should render the success page on successful signup" do
post :create, :user => #params
expect(response).to be_successful
expect(response).to render_template("success")
end
Finally, change
render :template => "success"
to
render :action => "success"
For previous RSpec versions or if you have to use the should syntax, use
before(:each) do
#params = valid_attributes
end
it "should create new user" do
#_before = User.count
post :create, :user => #params
assigns(:user).should_not be_new_record
User.count.should == (#_before + 1)
end
it "should render the success page on successful signup" do
post :create, :user => #params
response.should be_successful
response.should render_template("success")
end
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