How do you cause an expectation to halt execution in RSpec? - ruby-on-rails

I want to test that a class receives a class-method call in RSpec:
describe MyObject do
it "should create a new user" do
expect(User).to receive(:new)
MyObject.new.doit
end
end
class MyObject
def doit
u = User.new
u.save
end
end
The problem is that the expectation does not halt execution. It simply stubs the class method .doit and continues execution.
The effect of the expectation is to ensure that User.new returns nil. So when we get to the next line which is User.save it then fails because there is no user object to call .save on.
I would like execution to halt as soon as the RSpec expectation has been satisfied - how can I do that?
nb
This is just an illustrative example - while an expect to change would work for User.new, it's not this actual code that I need to test

There is a great method for this and_call_original:
expect(User).to receive(:new).and_call_original

based on your test description, you're testing that a record was created, in those cases I would suggest you to do this:
expect {
MyObject.new.doit
}.to change{User.count}
or if you want to make sure it only created one:
expect {
MyObject.new.doit
}.to change{User.count}.by(1)

Related

how to check if a method is called or not in rspec

as i try to check internally my method was calling or not in rspec but it got the following errors
context "#Meeting_schedule" do
let(:meeting_schedule) { FactoryGirl.create(:meeting_schedule,:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]) }
it "if the same schedule was created again dont save it again" do
schedule.save
params = {:time=>"morning",:schedule_name=>"planned_meet", :schedule_info=>[{ "from"=>"00:00", "to"=>"00:01"}]}
meeting_schedule.create_or_update_meeting_schedule(params)
expect(meeting_schedule).to receive(:updating_the_user)
end
end
i got the following error
Failure/Error: expect(meeting_schedule.create_or_update_meeting_schedule(params)).to receive(:updating_the_user)
(#<Meeting_schedule:0x0055dbaf0da710>).updating_the_user(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/models/meeting_schedule_spec.rb:122:in `block (4 levels)
so what was wrong in my code?
my method
def create_or_update_meeting_schedule(params)
self.attributes = params
if self.changed and self.save
updating_the_user
end
self
end
can anyone help me out
Mocks must always be setup before the method under test is called as there is no reliable way to test if a normal method was called in Ruby. There are two ways of doing this in RSpec.
The first is using expect(...).to receive(...) which must be done before the method is called - this detaches the method and replaces it with a mock that wraps the original method.
The test will fail if the method is not called in the example.
The second is by using spies. You can either replace an entire object with a spy:
RSpec.describe "have_received" do
it "passes when the message has been received" do
invitation = spy('invitation')
invitation.deliver
expect(invitation).to have_received(:deliver)
end
end
This "spy object" will keep track of any method you call on it.
You can also spy on a single method:
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
it "passes when the expectation is met" do
allow(Invitation).to receive(:deliver)
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end
Spies are very useful in the case when you want to mock the method in the test setup - for example in the before block.
class Invitation
def self.deliver; end
end
RSpec.describe "have_received" do
before do
allow(Invitation).to receive(:deliver)
end
it "passes when the expectation is met" do
Invitation.deliver
expect(Invitation).to have_received(:deliver)
end
end

How to check if the inside method has been called using RSpec

I got a method to update the person by id:
def update_person(id)
handle_exceptions do
person = Person.find(id)
#...other
end
end
When this id doesn't exist, the handle_exception should be called. But how could I test it? The test I wrote is:
context 'not found the proposals' do
subject {controller.send(:update_person, 3)}
before do
allow(Person).to receive(:find).and_raise(ActiveRecord::RecordNotFound)
allow(subject).to receive(:handle_exceptions)
end
it 'calls handle_exceptions' do
expect(subject).to have_received(:handle_exceptions)
end
end
But it not works, I got a failure said:
Failure/Error: expect(subject).to have_received(:handle_exceptions)
({:message=>"Not Found", :status=>:not_found}).handle_exceptions(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
The handle_exceptions method is
def handle_exceptions
yield
rescue ActiveRecord::RecordNotFound => e
flash[:warning] = 'no record found'
Rails.logger.error message: e.message, exception: e
#error_data = { message: 'no record found', status: :not_found }
end
The problem is that you are calling the method under test in the subject block.
subject {controller.send(:update_person, 3)}
This is actually called before the example runs and before the before block.
context 'not found the proposals' do
before do
allow(subject).to receive(:handle_exceptions)
end
it 'calls handle_exceptions' do
controller.send(:update_person, "NOT A VALID ID")
expect(subject).to have_received(:handle_exceptions)
end
end
But as far as tests go this one is not good. You're testing the implementation of update_person and not the actual behavior. And you're calling the method with update_person.send(:update_person, 3) presumably to test a private method.
You should instead test that your controller returns a 404 response code when try to update with an invalid id. Also why you insist on stubbing Person.find is a mystery since you can trigger the exception by just passing an invalid id. Only stub when you actually have to.
After couple days working, I realized the reason I'm confused about it is I didn't figure out about 'who called this function', and I think it's the most important thing to know before test it. For the method like this:
class User::Controller
def methodA
methodB
end
def methodB
// ...
end
The mistake that I made is I thought the methodB is called by methods, but it's not. It's called by the controller, and that's the reason that I can't make the test works. There's so many things need to learn, and I hope there's one day that I won't have a mistake like this and be able to help others.

Using Rspec should_receive to test that a controller calls a method on an object correctly

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.

RSpec: expect.to receive fails if object is not referenced directly

In my Rails application I have a User model:
class User
def self.foo
User.all.each{ |user| user.bar }
end
def bar
end
end
In my spec file I want to check that foo calls bar for every user, so far that's what I have:
describe '::foo' do
let!(:users) { Fabricate.times(5, :user) }
it 'calls bar for every user' do
users.each do |user|
expect(user).to receive(:bar)
end
User.foo
end
end
Although the method gets called (I debugged it, so I'm sure of that) the spec is red.
Also I tried to write this code to understand where the problem was:
let!(:user) { Fabricate(:user) }
it 'fails' do
expect(user).to receive(:bar)
User.first.bar
end
it 'pass' do
expect(user).to receive(:bar)
user.bar
end
It seems that if I reference my instance directly it works, if I obtain it from the DB the expectation doesn't work.
I use mongoid, not sure if this is relevant.
I believe it cannot be done due to how RSpec works: When you set an expectation, RSpec essentially 'wraps' the object so that it can keep track of the messages it receives.
But when the implementation code fetches records from the database, they are not wrapped, so RSpec isn't able to record their messages.
RSpec does have a method allow_any_instance_of which can help in some cases, but its use is discouraged, and don't think it would be suitable here.
In this situation, I would suggest stubbing User.all to return some doubles (two should be sufficient). You can then verify that bar is called on each one.

rspec expect redirect_to fail

Sometimes new (very DRY) rspec syntax makes me crazy...
Rspec v 2.14.1
describe "POST create" do
subject { post :create, contractor: valid_params }
context "by user" do
before { sign_in #legal.user }
it "contractor successful created" do
expect { subject }.to redirect_to(contractor_path(assigns(:contractor).id))
I have error & question here:
NoMethodError: # :contractor variable not defined
undefined method `id' for nil:NilClass
It seems that expect take an operator before controller method post executes, because I try to raise this method.
My code:
def create
#contractor = Contractor.restrict!(current_accreditation).new(permitted_params) # TODO move to the IR::Base
if #contractor.save
current_accreditation = #contractor.create_legal!(user: current_user) # TODO legal create
redirect_to(#contractor)
else
render(:new)
end
end
Secondly, why I have an error when try
expect(subject).to ...
Why {} works, but () no? In relish docs this method work great: https://www.relishapp.com/rspec/rspec-rails/docs/matchers/redirect-to-matcher
Kinda unrelated but I've found the following helpful:
Use expect {} when you want to test before/after of whatever's in the block. eg. expect { subject } to change(User, :count) - you want to check the count before, then after, to work out the difference and see if it actually changed.
Use expect () to verify the outcome of a block, eg. that a redirect occurred, or something got assigned, etc.
Your test with the assigns(:contractor) doesn't work because you're using the {} notation - so it's trying to work out the assigns(:contractor).id both before and after evaluating the subject (and of course, before the subject, it doesn't exist).

Resources