How to test chain call in RSPEC? - ruby-on-rails

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.

Related

Do I need to stub EVERYTHING in this method in order to pass?

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.

How to return a value when using execute_script in capybara?

I have a really simple line in a test that calls execute script like this:
puts page.execute_script("return somefunction();").to_i.inspect
And in javascript I have a function like this:
function somefunction(){
console.log("test");
return 999;
}
The 'test' from the console log is getting logged out so it is running however...
Looking at the logs when running the test, the execute_script returns 0 not 999, so in rspec I can't get at the return value from the function, how do I make page.execute_script return that value from that function?
The Poltergeist driver is designed to return nil for execute_script:
def execute_script(script)
browser.execute(script)
nil
end
Poltergeist will only return a value if you use the evaluate_script:
def evaluate_script(script)
browser.evaluate(script)
end
Capybara has corresponding methods for each - ie Session#execute_script and Session#evaluate_script. Your code should work if you switch to using evaluate_script (and as #AndreyBotalov points out, you also need to remove the return):
puts page.evaluate_script("somefunction();").to_i.inspect

no block given yield

So executing this gives me back an error:
no block given (yield)
Well never had a deep look at blocks in ruby, which seems to be an issue in here. If you have a better solution please provider, otherwise I wanted to find a workaround for the this legacy code...
def tab_groupings
result = at_a_glance_grouping
result += rating_grouping if #domain_context.include_ratings and (controller.controller_name !='rewards_credit_cards')
result += specific_tab_groupings
result
end
def at_a_glance_grouping
result = [[:at_a_glance, yield]]
product_type = controller.controller_name == 'fairfax' ? #product_type_helper[:controller] : controller.controller_name
result[0][1].insert(0, :overall_rating) if #domain_context.include_ratings and (product_type !='rewards_credit_cards')
result
end
yield is used to execute a block that you pass to the method, and then you do something with the result of that call.
Your method at_a_glance_grouping therefore expects you to pass a block to it... which it will then execute on the following line (where you use yield)
You don't pass any blocks to at_a_glance_grouping in the first line of tab_groupings, and therefore ruby rightfully complains.
What are you trying to achieve with the yield ?
Do you really need it at all?
If not - then just remove it.
If sometimes you do pass a block to this method, then you need to check for that before calling yield eg:
result = [[:at_a_glance, yield]] if block_given?

Capybara: Test that JavaScript runs without errors

I would like to write a request spec that verifies the loading and execution of javascript on a given page without any errors.
I know I can add something to the DOM at the end of my JavaScript file and assert the presence of this content, but that feels like a hack and forces me to pollute my code for testing reasons. I'd prefer to do something along the lines of.
visit some_path
page.should succesfully_run_javascript
You can achieve this with the following piece of code in your tests.
page.driver.console_messages.each { |error| puts error }
expect(page.driver.console_messages.length).to eq(0)
The first line prints out an errors, handy for seeing what's going on. The second line causes the test to fail.
One way that's potentially not very polluting is to collect any errors by adding something like this to head (keep in mind this will override any onerror handler you may already have):
<script>
window.errors = [];
window.onerror = function(error, url, line) {
window.errors.push(error);
};
</script>
You could then do something like:
page_errors = page.evaluate_script('window.errors')
and assert on the array being empty. You could output the errors otherwise...
Note: It's important to add the scriptlet to head (potentially as the first scriptlet/script tag) as it needs to be one the first executed scripts.
A problem with using page.driver.console_messages is that it doesn't clear between tests. So if you want to only assert that there are no errors on a particular test the spec might fail due to a previous test.
You can scope these errors to a particular spec by saving the last console.log timestamp.
Helper method:
def assert_no_js_errors
last_timestamp = page.driver.browser.manage.logs.get(:browser)
.map(&:timestamp)
.last || 0
yield
errors = page.driver.browser.manage.logs.get(:browser)
.reject { |e| e.timestamp > last_timestamp }
.reject { |e| e.level == 'WARNING' }
assert errors.length.zero?, "Expected no js errors, but these errors where found: #{errors.join(', ')}"
end
And then use it like:
def test_somthing_without_js_errors
assert_no_js_errors do
# TODO: Write test
end
end

How to defer execution of expensive create_by option

The following question is almost exactly what I need: https://stackoverflow.com/a/2394783/456188
I'd like to run the following:
find_or_create_by_person_id(:person_id => 10, :some_other => expensive_query)
But more importantly, I'd like to defer the execution of expensive_query unless I actually have to create the object.
Is that possible with find_or_create_by?
Turns out find_or_create_by* accepts a block that only gets run in the create case.
find_or_create_by_person_id(10) do |item|
item.some_other = expensive_query
end

Resources