I want to test a method which makes a call to a service, if the call to that service times out I am displaying a negative feedback to user. How to unittest the timeout use case ??
My method looks like:
def method
x = callservice()
if x[:value]
display_positve_feedback("positive")
else
display_negative_feedback("negative")
end
rescue Timeout::Error => e
display_negative_feedback("Timeout, please wait for 5 mins and check again")
end
end
I have mocked callservice but how to I make that service Timeout to check the timeout use case???
You don't use a return code. If you add it, you could test for the method result.
Example:
def method
begin
x = callservice()
if x[:value]
display_positve_feedback("positive")
return true
else
display_negative_feedback("negative")
return false
end
rescue Timeout::Error => e
display_negative_feedback("Timeout, please wait for 5 mins and check again")
return nil
end
raise "This should never happen"
end
Now you can test on true, falseor nil.
Suppressing exceptions is usually not a good practice that I would recommend to anyone.
When you catch (rescue) the timeout exception, re-raise it (or your own application-specific one) in the rescue code and have the caller (your test) check that an exception is thrown for the timeout.
You could use assert_raise or expect raise_error, for example, depending on what testing framework you use.
Using an exception, the calling code only needs to know about a potential exception rather than having to check for specific return values, which makes the code simpler and more clear.
Stub the callservice method to raise a Timeout::Error. How you'll do that depends on what testing framework you're using. For example, in RSpec it might look something like this:
my_obj.stub(:callservice) { raise Timeout::Error }
expect(my_obj).to receive(:display_negative_feedback)
.with("Timeout, please wait for 5 mins and check again")
my_obj.method
Related
I have a while do loop in rails as
session['SPEAKERS'].map do |speaker|
begin
Component::AgendaSpeaker.create_with(
name: [speaker['FIRST_NAME'], speaker['LAST_NAME']].join(' '),
job_title: speaker['TITLE'],
remote_file_url: speaker['PHOTO_URL'],
description: speaker['EXPERTISE']
).find_or_create_by(
component_id: component_id(:agenda_speakers),
weg_id: speaker['SPEAKER_ID']
)
rescue
p "Rescue reached"
ensure
p "Ensure reached"
end
end
For each execution of the loop, I try to create and save a speaker and sometimes there raises an exception in the active_record (due to non-availability of the image file or 403 forbidden exception etc).
I would want to catch that in my rescue block and process it further. However, the exception is caught and rescued elsewhere (within the active_record) which throws an exception in my console directly, and hence the code won't reach my 'rescue' block at all. However, my code reaches 'ensure' block.
How do I change my code to reach my 'rescue' block?
You're looking for find_or_create_by!
https://apidock.com/rails/ActiveRecord/Relation/find_or_create_by%21
Lots of active record methods will fail simply by returning false (save, update, create etc..), but will throw an error when an exclamation mark gets added at the end
I'm trying to mock the code below using MiniTest/Mocks. But I keep getting this error when running my test.
Minitest::Assertion: unexpected invocation: #<Mock:0x7fa76b53d5d0>.size()
unsatisfied expectations:
- expected exactly once, not yet invoked: #<Mock:0x7fa76b53d5d0>.getresources("_F5DC2A7B3840CF8DD20E021B6C4E5FE0.corwin.co", Resolv::DNS::Resource::IN::CNAME)
satisfied expectations:
- expected exactly once, invoked once: Resolv::DNS.open(any_parameters)
code being tested
txt = Resolv::DNS.open do |dns|
records = dns.getresources(options[:cname_origin], Resolv::DNS::Resource::IN::CNAME)
end
binding.pry
return (txt.size > 0) ? (options[:cname_destination].downcase == txt.last.name.to_s.downcase) : false
my test
::Resolv::DNS.expects(:open).returns(dns = mock)
dns.expects(:getresources)
.with(subject.cname_origin(true), Resolv::DNS::Resource::IN::CNAME)
.returns([Resolv::DNS::Resource::IN::CNAME.new(subject.cname_destination)])
.once
Right now you are testing that Resolv::DNS receives open returns your mock but
since you seem to be trying to test that the dns mock is receiving messages you need to stub the method and provide it with the object to be yielded
Try this instead:
dns = mock
dns.expects(:getresources)
.with(subject.cname_origin(true), Resolv::DNS::Resource::IN::CNAME)
.once
::Resolv::DNS.stub :open, [Resolv::DNS::Resource::IN::CNAME.new(subject.cname_destination)], dns do
# whatever code actually calls the "code being tested"
end
dns.verify
The second argument to stub is the stubbed return value and third argument to stub is what will be yielded to the block in place of the original yielded.
In RSpec the syntax is a bit simpler (and more semantic) such that:
dns = double
allow(::Resolv::DNS).to receive(:open).and_yield(dns)
expect(:dns).to receive(:getresources).once
.with(subject.cname_origin(true), Resolv::DNS::Resource::IN::CNAME)
.and_return([Resolv::DNS::Resource::IN::CNAME.new(subject.cname_destination)])
# whatever code actually calls the "code being tested"
You can write more readable integration tests with DnsMock instead of stubbing/mocking parts of your code: https://github.com/mocktools/ruby-dns-mock
I want to use a rspec to simulate a flakey service handling.
For that, I want to make the service call raise an exception for a few times and after those times to return the real value.
Is this possible with rspec?
I tried with
allow(Service).to receive(:run).once.and_raise(MyError)
allow(Service).to receive(:run).once.and_return(response)
but on the first run it returns the response and not the error
You can accomplish this with a block implementation for the response.
call_count = 0
allow(Service).to receive(:run) do
call_count += 1
call_count < 3 ? raise(MyError) : response
end
Following #Ruy_Diaz suggestion you can also use the args to determine when to raise the error.
Example:
allow(Service).to receive(:run) do |args|
raise MyError if args[:object].type == 'some_type_to_raise_error'
response_that_the_service_is_expecting
end
I'm using the following function in Ruby on Rails:
def isGoogleEmailAddress?(email_domain)
Resolv::DNS.open({:nameserver=>["8.8.8.8"]}) do |r|
mx = r.getresources(email_domain,Resolv::DNS::Resource::IN::MX)
if mx.any? {|server| server.exchange.to_s.downcase.include? "google"} then
return true
end
return false
end
end
Is there a way to handle the issue where Resolv fails, timeouts, errors etc?
Look through the documentation for the Resolv class and add exception handlers for the various errors/exceptions the class can raise.
They're easy to pick out. Look for classes ending in error and timeout.
I use mini_test for testing. I have a code part like below.
raise Type_Error, 'First Name must not be empty' if #person.first_name == nil
How can I write this code's test?
Thanks...
I think you want assert_raises, which asserts that the block/expression passed to it will raise an exception when run.
For example, one of my projects has the following minitest:
test 'attempting to create a CreditCard that fails to save to the payment processorshould prevent the record from being saved' do
assert_equal false, #cc.errors.any?
failure = "simulated payment processor failure"
failure.stubs(:success?).returns(false)
failure.stubs(:credit_card).returns(nil)
PaymentProcessor::CreditCard.stubs(:create).returns(failure)
assert_raises(ActiveRecord::RecordNotSaved) { create(:credit_card) }
end
What this does, in my case, is:
Create a simulated failure message from the payment processor API
Stub the creation of a credit card on the payment processor to return the simulated failure
Try to create a credit card, simulating that the payment processor has returned a failed status, and assert that my internal save/create method throws an error under these conditions
I should say that this test code includes things in addition to minitest, such as FactoryGirl, and (I think) shoulda and mocha matchers. In other words, what is shown above isn't strictly minitest code.
raise Type_Error, 'First Name must not be empty' if #person.first_name == nil
For testing above line, I wrote a test like below. I used minitest::spec for this.
def test_first_name_wont_be_nil
person.name = nil
exception = proc{ Context.new(person).call }.must_raise(TypeError)
exception.message.must_equal 'First Name must not be empty'
end
Context is place where make some process.