I have the following
it 'should assign a new profile to user' do
get :new
assigns(:user_profile).should ==(Profile.new)
end
But it's not working. I've tried 'eql?' and 'equal?' respectively. How do I compare it in order to know if the contents of #user_profile is Profile.new?
I used to do a workaround doing a .class of the assigned variable, checking if it's Profile but I want to stop with these bad practices.
Thanks.
The problem here is that Object.new invoked twice by design creates two different objects, which are not equal.
1.9.2p318 :001 > Object.new == Object.new
=> false
One thing you can do here is
let(:profile){ Profile.new }
it 'should assign a new profile to user' do
Profile.should_receive(:new).and_return profile
get :new
assigns(:user_profile).should eq profile
end
Now you're not actually creating a new profile when the controller action is invoked, but you are still testing that Profile is receiving new, and you're testing that the return value of that method is being assigned by the controller to #user_profile.
Related
I have a User class with a save method which makes a change to one of the user instance attributes. Specifically, each user has an options hash that gets one of its values deleted during the save process.
I have an rspec test with 2 context groups. Each group creates a new #user object using FactoryGirl.build(:user). When I call #user.save in the first context group, the attribute change occurs as expected. However, the second time that FactoryGirl.build(:user) gets called, it doesn't return a User object according to the FactoryGirl definition. It returns a user object with an options hash that is missing the same value that gets deleted during the save process. This object is not valid, and as a result #user.save fails the second time.
UPDATE: I tried changing the variable names and I still have the same problem. The issue seems to be with the FactoryGirl :user factory being modified somehow during the first example, resulting in the second example failing.
Below is a simplified version of my code. Whichever context group is executed second ("with avatar" or "without avatar") when run randomly by Rspec is the one that fails. I have used puts in both cases to confirm that the second #user has a bad options hash, and causes the test to fail.
describe "save" do
context "with avatar" do
before(:context) do
#user = FactoryGirl.build(:user)
puts #user
#save_result = #user.save
end
after(:context) do
delete_user(#user)
end
it "should return true" do
expect(#save_result).to be true
end
end
context "without avatar" do
before(:context) do
#user = FactoryGirl.build(:user, avatar: nil)
puts #user
#save_result = #user.save
end
after(:context) do
delete_user(#user)
end
it "should return true" do
expect(#save_result).to be true
end
end
end
I suspect that the options hash gets reused.
According to the FactoryGirl readme, when you want to add a hash attribute to a FactoryGirl definition and that hash is dynamic (i.e. not the same among all created instances), you need to wrap it in a block:
Instead of:
factory :user do
options { option1: 1, option2: 2 }
end
You need to do:
factory :user do
options { { option1: 1, option2: 2 } }
end
I'm writing a pretty straightforward method. Whenever a referer has referred 5 people to become new users, I want them to get a refund. This means that when a new user is created, there's a method check_referer that checks to see if the person who referred them (if this person exists) should get a refund because they've now referred 5 people in total.
In the test logs, based on the puts statement, I can tell that the code is working and the refund_membership_fees_paid method is indeed being called once. But the test keeps failing with:
Failure/Error: #referer.should_receive(:refund_membership_fees_paid).exactly(1).times
(#<User:0x007fbf46bf1c58>).refund_membership_fees_paid(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
Test code:
describe User, "Test refund_membership_fees_paid method is called" do
before do
#referer = User.new()
#referer.save(validate:false)
RefererRefundAlert.stub_chain(:new, :async, :perform)
end
it "at 5 users" do
5.times do |index|
u = User.new(referred_by: #referer.id)
u.save(validate:false)
end
#referer.should_receive(:refund_membership_fees_paid).exactly(1).times
end
end
Model code:
def check_referer
if self.referred_by.present? && User.where(referred_by: self.referred_by).count == 5
User.find(self.referred_by).refund_membership_fees_paid
end
end
def refund_membership_fees_paid
puts "refund_membership_fees_paid method"
RefererRefundAlert.new.async.perform(self.id)
end
User.find does not return the same object as #referer; it will return a different instance of User that represents the same user in the database.
Instead of checking whether refund_membership_fees_paid is called, you can verify that the correct user ID is getting passed intoRefererRefundAlert.new.async.perform.
Also, as others mentioned, you should set your expectation before running the tested methods.
RefererRefundAlert.new.async.should_receive(:perform)
.with(#referer.id).exactly(1).times
5.times do |index|
u = User.new(referred_by: #referer.id)
u.save(validate:false)
end
The use of should_receive is to set the expectation for the following action.
For example, if your account.close action is supposed to log the closure, the test would be...
logger.should_receive(:account_closed)
account.close
So your example should be restructured to put the test first...
#referer.should_receive(:refund_membership_fees_paid).exactly(1).times
5.times {User.new(referred_by: #referer.id).save(validate: false)}
OK first, I should say while I've read a lot about should_receive, I'm still not entirely sure I'm understanding the concept behind it, so what I'm doing could potentially be completely not possible.
I have the following:
class PlansController
def destroy
plan = plan.find_by_id(params[:id])
if plan.cancel_stripe_subscription(params[:reason])
flash[:success] = "success"
redirect_to root_path
else
#error handling
end
end
end
class Plan
def cancel_stripe_subscription(reason)
self.status = "canceled"
self.cancellation_reason = reason
if self.save
return true
else
return false
end
end
In my controller spec, I am thinking it makes sense to do a test that the cancel_stripe_subscription method is called successfully (using 1should_receive1), with the right arguments and everything, and another test that the output of the destroy action is correct.
In other words, I thought to write the following controller spec:
describe PlansController, "Destroy Action" do
before do
#plan = Plan.create(...)
end
it "should have called destroy action" do
delete :destroy,
plan: {
id: #plan.id,
reason: "something"
}
assigns(:plan).should_receive(:cancel_stripe_subscription).with(reason:"something").exactly(1).times.and_return(true)
end
it "should have called destroy action" do
delete :destroy,
plan: {
id: #plan.id,
reason: "something"
}
assigns(:plan).status.should == "canceled"
assigns(:plan).cancellation_reason.should == "something"
end
end
The second test passes, but the first throws
Failure/Error: assigns(:plan).should_receive(:cancel_stripe_subscription)
(#<Plan:0x007fe282931310>).cancel_stripe_subscription(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
So I really have two questions:
Just to confirm, am I using should_receive correctly? Should I even be testing for this? Or is the second test generally accepted as enough?
If I should be testing for this, what's the right way of using should_receive? (Note, have not had luck with expect(#plan).to have_received(:cancel_stripe_subscription) either)
I think the confusion here is in part due to combining two different styles of testing, mockist (your first test) and classicist (your second test). It's fine to use one or the other, based on your preferred testing style, but using both together to test the same piece of code is somewhat redundant.
A should_receive expectation has to be set before you call the method under test; you're setting it afterwards. Since you need to set it before, you then have to make sure the object you've set up the expectation on ends up being operated on in the action. The normal way would be to stub out find_by_id on Plan, like this:
it "should have called destroy action" do
Plan.stub(:find_by_id).and_return(#plan)
assigns(:plan).should_receive(:cancel_stripe_subscription).with(reason:"something").exactly(1).times.and_return(true)
delete :destroy, plan: { id: #plan.id, reason: "something" }
end
(I am assuming that you meant to write plan = Plan.find_by_id(params[:id]) in the first line of your destroy action.)
As to whether you should be testing it this way, I'd say that your second test does a good enough job of verifying the outcome that you want, and you don't really need to go to all the trouble.
I have a method that essentially clones a record. Then redirects to the cloned records edit page. I have a mini test that needs to test that the record gets cloned and redirected to the new records edit path.
#new_record is a new object created in the controller method
describe 'clone_record' do
it 'should clone record' do
#first_record = FactoryGirl.create(:blah)
get :clone_record, :first_record_id => #first_record, :new_record => 'new_record'
assert_redirected_to(edit_record_path(#new_record.id))
end
end
How do I get the value of #new_record in the test?
If you want the most recently created record Record.last should work.
I'm trying to firm up my understanding of why this actually works
My application is just the default scaffold for the purposes of this experiment.
it "creates a record and redirects with success notice" do
Project.any_instance.stubs(:valid?).returns(true) #ensure action behaves as if #project was valid
post :create, :format => :js
assigns(:project).errors.should be_empty #ensures that #project has no errors
flash[:notice].should_not be_nil #ensure a flash notice is set
response.should redirect_to(projects_path) #ensure appropriate redirection
end
end
In the controller the save method is called on #project. Here is where I become unsure. The object will save because the valid? method has been stubbed to return true, even if the object is not valid. But if the object is not valid how can the save be successful?
Rails is also calling your valid? stub internally before saving, so it proceeds with the save as normal because it thinks nothing is wrong.
On the other hand if you had, say, a UNIQUE index on a field in a MySQL database, a duplicate row there could still prevent the record from being saved, even though you stubbed valid?. Two possible layers of validation.