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
Related
I have call looks like
1) foo1 => MyModel.where(id: my_model_ids)
2) foo2 => MyModel.where('date > :new_date', {new_date: DateTime.new(0)})
3) foo2.sum(:count)
How can I test this call Chain ?
I tried this
where_mock = instance_double(ActiveRecord::Relation)
result_mock = instance_double(ActiveRecord::Relation)
filter = 'date > :new_date'
new_data_hash = {new_date: DateTime.new(0)}
expect(where_mock).to receive(:where).with(filter, new_data_hash).and_return(result_mock)
expect(result_mock).to receive(:sum)
But it didn’t work
I think you're looking for receive_message_chain, https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/message-chains
The example they give is:
allow(Article).to receive_message_chain("recent.published") { [Article.new] }
It's worth noting that use of this method is often symptomatic of a code smell.
If here you're testing that a message is received rather than the output, you can test the first method call in isolation, stub the result, and then test that the stub receives the second method call.
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?
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 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.
I'm currently fighting with the rails class_caching mechanism as I need to return a file path that changes discrete over time. It is used to constantly change a log file path after the amount of GRAIN seconds and returns a fully working timestamp:
GRAIN = 30
def self.file_path
timestamp = (Time.now.to_i / GRAIN) * GRAIN
return FILE_DIR + "tracking_#{timestamp.call}.csv"
end
This works really great if the class_caching of rails is set to false. But of course the app is to run with enabled class caching. And as soon as I enable it, either the timestamp variable is cached or the Time.now expression.
I tried to solve this with a proc block, but no success:
def self.file_path
timestamp = Proc.new { (Time.now.to_i / GRAIN) * GRAIN }
return FILE_DIR + "tracking_#{timestamp.call}.csv"
end
Is there anything like a cache disabled scope I could use or something like skip_class_caching :file_path? Or any other solutions?
Thank you for your help!
It's not entirely clear where your code is located, but ActiveRecord has an uncached method that suspends the cache for whatever is inside its block.
I found the problem. Not the Time.now was beeing cached but a logger instance. It was assigned in another method calling the file_path.
As long as the class caching was disabled the environment forgot about the class variable between the requests. But as soon as it was enabled the class variable stayed the same - and desired value - but never changed.
So I had to add a simple condition that checks if the file_path changed since the last request. If so, the class variable is reassigned, otherwise it keeps the same desired value.
I changed from:
def self.tracker
file_path = Tracking.file_path
##my_tracker ||= Logger.new(file_path)
end
to:
def self.tracker
file_path = Tracking.file_path
##my_tracker = Logger.new(file_path) if ##my_tracker.nil? or Tracking.shift_log?(file_path)
##my_tracker
end
Thank you for your help anyways!