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.
Related
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
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)
I have an ActiveRecord Transaction that I am trying to test, but I'm having some difficulty understanding exactly how to write my Rspec test. Here is an example of what I'm going for:
it "does not change the model count" do
expect(Model.count).to be(0)
expect {
MyClass.my_method(arg1, arg2)
}.to raise_error
expect(Model.count).to be(0)
end
While my_method is running, there are several objects being saved to the DB. I want to raise an exception while this method is running in order to invoke the transaction rollback.
What is the best way to go about raising this exception?
EDIT:
I appreciate everyone taking the time to give me input. My goal was to test a transaction rollback. I was calling two different methods inside a transaction, and I wanted to make sure that if an error arose in the second method the data written to the DB in the first method did not persist.
I got this to work as I needed, though I recognize it's contrived.
class MyClass
def self.save_my_values(arg1, arg2)
parsed = parse(arg1)
ActiveRecord::Base.transaction do
some_method(parsed, arg2)
my_method(parsed, arg2)
end
end
end
And here is the code in the spec:
context "when there is an error" do
before do
allow(MyClass).to receive(:my_method).and_raise(StandardError)
end
it "does not change the model count" do
expect(Model.count).to be(0)
expect {
MyClass.my_method(arg1, arg2)
}.to raise_error(StandardError)
expect(Model.count).to be(0)
end
end
I'll be refining it further, but this was the starting point I was looking for. Thank you again!
I have an rspec that looks like:
it "should test nil" do
u = Upload.new
u.save!
u.my_method!
end
The method looks like:
class Upload < ActiveRecord::Base
def my_method!
puts "Made it to my method :)"
not_defined_var
puts "But didn't get this far :("
end
end
Clearly, not_defined_var should give a "NameError: undefined local variable or method".
My output prints
Made it to my method :)
.
Finished in 1.54 seconds
1 example, 0 failures
I feel like I'm missing something essential. Got any ideas?
You are not calling method expect so Rspec is just passing. To compare if anything goes as expected you must call it. To test code you must compare the response with what you expect.
Example:
RSpec.describe Account do
it "has a balance of zero when first created" do
expect(Account.new.balance).to eq(Money.new(0))
end
end
https://relishapp.com/rspec/rspec-expectations/v/3-3/docs
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).