I’m writing regression tests for a part of a Rails application that looks like this:
Timeout.timeout(mode == :index ? 6 : 45) do
imap = Net::IMAP.new(...)
end
I want to verify that in each case (i.e., mode == :index & mode != :index), the correct Timeout length is set.
I tried using ActiveSupport's travel:
allow(Net::IMAP).to receive(:new).and_wrap_original do |m, *args|
travel 6
m.call(*args)
end
expect { method }.to raise_error(Timeout::Error)
but it doesn’t trigger the Timeout. If I use sleep instead then it does work, but obviously, I’m not putting sleep 45 in a unit test.
How can I simulate the passage of time to trigger a Timeout in a test?
Related
I am generally new to mocha and I hate using this gem but I need to use it in order to pass a test that I am constructing. What is giving me problems is what I'm supposed to mock and how I am supposed to mock it. To illustrate my point, Here is an example of a method that I am testing:
def statistics_of_last_24_hrs
stats = ses.statistics.find_all { |s| s[:sent].between?(Time.now.utc - 24.hours, Time.now.utc) }
sent_last_24_hrs = ses.quotas[:sent_last_24_hours].to_f
no_of_bounces = stats.inject(0.0) { |a, e| a + e[:bounces] }
no_of_complaints = stats.inject(0.0) { |a, e| a + e[:complaints] }
bounce_rate = sent_last_24_hrs.zero? ? 0.0 : (no_of_bounces / sent_last_24_hrs) * 100
complaint_rate = sent_last_24_hrs.zero? ? 0.0 : (no_of_complaints / sent_last_24_hrs) * 100
fail(Reverification::SimpleEmailServiceLimitError, 'Bounce Rate exceeded 5%') if bounce_rate >= 5.0
fail(Reverification::SimpleEmailServiceLimitError, 'Complant Rate exceeded .1%')if complaint_rate >= 0.1
end
Basically what this code is doing is getting some statistics from an Amazon api call and then calculating them to determine if my bounce/complaint rate has exceeded the limit. The limit is 5% and 0.1% respectively.
Basically for my test all I really need to do is to stub the variables bounce_rate and complaint_rate in order to test whether the right exception is thrown.
This is where I am getting stuck. Here is a barebones test that I would ideally write:
it 'should raise SimpleEmailServieLimitError if bounce rate is above 5%' do
assert_raise Reverification::SimpleEmailServiceLimitError do
Reverification::Process.statistics_of_last_24_hrs
end
end
How can I stub the bounce_rate and then the complaint_rate. I've done some searching around and came to the conclusion that there isn't a way to stub variables. I also looked at this link List of Mocha MethodsWhich confirms my findings.
Is there a way I can just write a test like this:
it 'should raise SimpleEmailServieLimitError if bounce rate is above 5%' do
stubs(:bounce_rate).returns(true)
assert_raise Reverification::SimpleEmailServiceLimitError do
Reverification::Process.statistics_of_last_24_hrs
end
end
Or do I have to stub every method call in this method so that the test will look something like this:
it 'should raise SimpleEmailServieLimitError if bounce rate is above 5%' do
sent_last_24_hrs = 20
over_bounce_limit = MOCK::AWS::SimpleEmailService.over_bounce_limit
AWS::SimpleEmailService.any_instance.stubs(:statistics).returns(stub(find_all: over_bounce_limit))
AWS::SimpleEmailService.any_instance.stubs(:quotas).returns(stub(sent_last_24_hours: sent_last_24_hrs))
etc. etc. etc...........
assert_raise Reverification::SimpleEmailServiceLimitError do
Reverification::Process.statistics_of_last_24_hrs
end
end
Is there an easier way to do this?
Even if there was a way to stub local variables, that feature would produce tests that are very hard to maintain because you'd not be able to refactor your code without changing tests.
Nested stubs are design smell too - your tests will know about too much implementation details and will become unmaintainable.
The same can be said for stubbing third-party code as any changes to the third-party library will allow your tests to pass while the code does not work.
It is a lot better to create your own wrapper around AWS SimpleEmailService - gateway. You'd implement it to have a very narrow stable interface like
class BounceStatistics
def no_of_bounces
def no_of_complaints
def sent_last_24_hrs
end
Since this interface is your own and it is stable, you can safely stub it and provide alternative implementation for your tests:
assert_raise Reverification::SimpleEmailServiceLimitError do
Reverification::Process.statistics_of_last_24_hrs(
stub(no_of_bounces: 2, no_of_complaints: 3, sent_last_24_hrs: 5))
end
alternatively you may implement it as
BounceStatistics.any_instance.stubs(:no_of_bounces).returns(2)
BounceStatistics.any_instance.stubs(:no_of_complaints).returns(3)
BounceStatistics.any_instance.stubs(:sent_last_24_hrs).returns(5)
assert_raise Reverification::SimpleEmailServiceLimitError do
Reverification::Process.statistics_of_last_24_hrs
end
However passing dependencies explicitly allows you to have more maintainable code and simpler tests.
I'm making a simple way to check is a site is up or not, this is my Ping model that holds a few adresses I want to check
require 'net/http'
def self.check
pings = Ping.all
pings.each do |p|
http = Net::HTTP.new(p.address,80)
response = http.request_get('/')
if response.message == ( 'OK' or 'Found')
puts 'up!!'
end
end
end
I'm just checking if the response message is "OK" or "Found" but my or statement only checks for "OK".
Also is this a good way to check?
response.message == ( 'OK' or 'Found') isn't going to do what you think.
> ( 'OK' or 'Found')
=> "OK"
This is why it's only checking for "OK". IMHO you shouldn't check the message as that could vary. Check the response code for anything in the 200 or 300 range.
As long as you aren't checking a lot of sites the above is fine. If you were, it might begin to take awhile as they are sequential.
You might also want to add a timeout so that if it's trying to ping and fails for a 20 seconds consider the site down.
Also I know for a fact that some websites will return 400 errors for unrecognized user agents. So what you see via your browser might not be at all what your script is going too.
As pointed out by #PhilipHallstrom, your if statement isn't doing what you think it's doing. (if a== (b or c) != if a == b || a == c ... Also, there is the issue of case sensitive equality.
Try a Regexp with the case insensitive configuration (the i at the end):
response.message.match /^(ok|found)$/i
... although...
I support #PhilipHallstrom's reservations about the code.
Numerical codes would probably be better and application responsiveness should be a factor in the design (I assume you have that one covered)...
I would probably go for:
response.code >= 200 && response.code < 400
Given all the hype over TDD, I decided it was time to dig in and add that to the list of things to study. I'm running into an issue, and I'm 100% certain it's just a function of something being wrong with my tests in RSpec. I'm still brand new to RSpec, so I'm having trouble figuring it out... my method works just fine, but the test for the method does not.
Method Code (I know I can refactor this A LOT. This is one of the first Ruby programs I wrote awhile back, which explains the ugliness)
def caesar_cipher(string,offset)
string=string.to_s
offset=offset.to_i
cipher=[]
string.each_byte do |i|
#capital letters
if (i>64 && i<91)
if (i+offset)>90
cipher << (i+offset-26).chr
else
cipher << (i+offset).chr
end
elsif (i>96 && i<123)
if (i+offset)>122
cipher << (i+offset-26).chr
else
cipher << (i+offset).chr
end
else
cipher << i.chr
end
end
cipher=cipher.join('')
puts "The encrypted string is: #{cipher}"
end
puts "Enter the string you'd like to encrypt"
string=gets.chomp
puts "Enter the offset you'd like to use"
offset=gets.chomp
caesar_cipher(string,offset)
Test Code (Just for one generic case, all lower case input)
require './caesarCipher.rb'
describe "caesar_cipher" do
it 'should handle all lower case input' do
caesar_cipher("abcdefg", 3).should == "defghij"
end
end
Method output:
$ ruby caesarCipher.rb
Enter the string you'd like to encrypt
abcdefg
Enter the offset you'd like to use
3
The encrypted string is: defghij
Test Output:
$ rspec spec/caesar_cipher_spec.rb
Enter the string you'd like to encrypt
Enter the offset you'd like to use
The encrypted string is: require './caesarCipher.rb'
The encrypted string is: defghij
F
Failures:
1) caesar_cipher should handle all lower case input
Failure/Error: caesar_cipher("abcdefg", 3).should == "defghij"
expected: "defghij"
got: nil (using ==)
# ./spec/caesar_cipher_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.00542 seconds (files took 0.14863 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/caesar_cipher_spec.rb:4 # caesar_cipher should handle all lower case input
Any help on why the tests are failing? Judging by the output it looks like it's running it twice or something in the tests.
Add cipher or return cipher after this line
puts "The encrypted string is: #{cipher}"
And it should work
To explain the fix given, the last expression in a method is the return value. You've passed the value to STDOUT but not as the return value of the method, so RSpec was failing.
I just can't seem to get a hold of what exactly stubs are.
Could someone just explain what the following RSPEC code is supposed to do. And what is the benefit of using stub here?
require "performance_monitor"
require "time" # loads up the Time.parse method -- do NOT create time.rb!
describe "Performance Monitor" do
before do
#eleven_am = Time.parse("2011-1-2 11:00:00")
end
it "takes exactly 1 second to run a block that sleeps for 1 second (with stubs)" do
fake_time = #eleven_am
Time.stub(:now) { fake_time }
elapsed_time = measure do
fake_time += 60 # adds one minute to fake_time
end
elapsed_time.should == 60
end
end
I think I'll be able to understand with an example.
stub is used here for override the function now of Time so here instead of return current time which you got from Time.now after stub it will return fake_time
Note that stub will only 'override' this method only in this one spec. Other specs will respond do Time.now properly
I'm trying to call two lengthy commands in a when statement, but for some reason, because of its syntax, it performs two of the commands twice when it is called :
#email = Email.find(params[:id])
delivery = case #email.mail_type
# when "magic_email" these two delayed_jobs perform 2x instead of 1x. Why is that?
when "magic_email" then Delayed::Job.enqueue MagicEmail.new(#email.subject, #email.body)
Delayed::Job.enqueue ReferredEmail.new(#email.subject, #email.body)
when "org_magic_email" then Delayed::Job.enqueue OrgMagicEmail.new(#email.subject, #email.body)
when "all_orgs" then Delayed::Job.enqueue OrgBlast.new(#email.subject, #email.body)
when "all_card_holders" then Delayed::Job.enqueue MassEmail.new(#email.subject, #email.body)
end
return delivery
How can I make it so that when I hit when "magic_email", it only renders both those delayed jobs once ?
I have tried this with following example:
q = []
a = case 1
when 1 then q.push 'ashish'
q.push 'kumar'
when 2 then q.push 'test'
when 4 then q.push 'another test'
end
puts a.inspect #######["ashish", "kumar"]
This is working fine. It means your case-when syntax is ok. It might be you have aome other problem.
You are calling return delivery and delivery varible may be having the value to call the delayed job again. It depends on what the then statement returns, so try not to return anything if possible. I believe you want to do the delayed job and not return anything by using the function.
Perhaps you should just have the case and dont store it in any variable. I mean delivery variable has no purpose here.