Rspec mocking block on stub not being yielded - ruby

So i have two calls to a method called retry_with_timeout that takes a block and executes until the block returns true or a value other than nil (i.e false will result in a loop) or until a timeout occurs
Sample class:
def do_the_thing
retry_with_timeout(10, 5) do
case something
when 1
false
when 2
false
else
raise
end
end
retry_with_timeout(30, 10) do
case something_else
when 1
false
when 2
false
when 3
true
else
raise
end
end
end
Spec class:
it "should pass when the thing is 3" do
model = test_model #creates a double and stubs all of the necessary common methods
t_model.stub(:retry_with_timeout).with(10, 5).ordered
t_model.stub(:retry_with_timeout).with(30, 10).and_yield().ordered
expect { t_model.do_the_thing }.to be(true)
end
I get an error because the '3' case isn't in the first block, thus the 'else' is called...
I need to skip the first and evaluate in the second block.... I have tried EVERYTHING and I am LOSING MY MIND!!!! Can anyone help me?

Ok, so I've answered my own question... Turns out there are some features that aren't documented... In order to return then yield, one must do it the following way:
t_model.stub(:some_method).and_return("Cool", "Awesome", and_yield(foo))
#Just for informations' sake
t_model.stub(:some_other_method).and_return("FOO", "BAR", raise_error)
its added as a return item for some reason and ISN'T DOCUMENTED ANYWHERE!!!!!

Related

Can you simultaneously test a state change and a return value in RSpec?

Say I have a method MyKlass#do_thing that I want to call exactly once in a test (because it might change a state), and that should return true on successful state change and false otherwise. I want to write a spec that looks something like this:
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
# ... assuming condition is met
expect { wojit.do_thing }.to change { wojit.value }.and.be true
end
But this particular approach gets an ArgumentError, because #and expects 1 argument.
I can make it work with the following abomination:
expect { expect(wojit.do_thing).to be true }.to change { wojit.value }
But that is hiiiiideous. Am I missing something more idiomatic?
Another approach is just to stick the return value in a variable.
return_value = nil
expect{ return_value = wojit.do_thing }.to change{ wojit.value }
expect( return_value ).to be true
YMMV as to whether it's better or worse than nested expects.
Maybe is not what you are looking for, but I actually think that "something more idiomatic" would be to make to tests using a describe or context block to express better that you testing the same case.
describe "When condition is met" do
it "updates the value" do
wojit = Wojit.new
expect { wojit.do_thing }.to change { wojit.value }
end
it "returns true" do
wojit = Wojit.new
expect(wojit.do_thing).to be_true
end
end
You could implement your own custom Matcher for this specific case like:
RSpec::Matchers.define :respond_with do |expected|
match do |actual|
actual.call == expected
end
# allow the matcher to support block expectations
supports_block_expectations
# make sure this executes in the correct context
def expects_call_stack_jump?
true
end
end
Then your expectation would be something like
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(respond_with(true))
end
The key here is that be,eq, etc. does not support block expectations and thus cannot be used in conjuction with expect{...} so we implemented an equality matcher that does support block expectations (supports_block_expectations? #=> true) and jumped it up the stack (this is very important in this case otherwise the change block creates a conflicting actual *Not sure I 100% understand why but trust me it does).
In this case actual will be the block body (as a Proc) so we just have to call it to compare the result to the expected value.
You could however abstract this out further to something like
RSpec::Matchers.define :have_response do |expectation|
supports_block_expectations
def expects_call_stack_jump?
true
end
#Actual matching logic
match do |actual|
#actual_value = actual.respond_to?(:call) ? actual.call : actual
expect(#actual_value).to(expectation)
end
failure_message do |actual|
"expected response to be #{expectation.expected} but response was #{#actual_value}"
end
failure_message_when_negated do |actual|
"expected response not to be #{expectation.expected} but response was #{#actual_value}"
end
end
#define negation for chaining purposes as needed
RSpec::Matchers.define_negated_matcher :not_have_response, :have_response
Which would allow you to use all the methods that do not support block expectations like so
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(have_response(be true))
# or
# expect{wojit.do_thing}.to not_have_response(be false).and(change(wojit, :value))
end
Only issue with either one of these approaches is that the block will be called once for the change and once for the response check so depending on your circumstances this could cause issues.

Range is printed at the end of the "each" iteration

What I'll show is a fulish function, but it's only to explain my problem in a simple way. If I solve the problem in this function, I solved the problem in the original code too.
The problem is simple, and probably the solution too, but I'm new in Ruby and have this doubt. I want to print the values in a range:
def test
(0...5).each do |i|
puts i
end
end
When I call the function, the result that I want is
0
1
2
3
4
but the result that I have is
0
1
2
3
4
0...5
Why this 0...5 is printed together? How can I avoid that?
i don't think the 0..5 is being produced as part of the puts call. Rather, when you call this in your REPL (irb, pry, rails console, etc), you're seeing because it's the last returned value in your code.
Let me show you an example.
Say I save a file called test.rb with the following content:
1.upto(5).each { |i| puts i }
If I call ruby test.rb, I see the expected output,
0
1
2
3
4
If I open irb and run require("./test.rb"), I see the same output.
It's only when I paste the code into irb that I see the additional output (=> 0...5). So I would just ignore this.
In addition to #Max's answer
Whenever any expression is executed in IRB sessions, it will also print the value returned by every expression executed.
In case of method definition it returns the method_name just defined.
> def my_method
?> puts 'this is my method'
?> end
=> :my_method
You see, the :my_method is printed
When the method is invoked, it should print the value returned by the method execution i.e. response of the last expression in the method i.e. puts
> my_method
this is my method
=> nil
but it printed nil because puts always returns nil. I mentioned this because normally developers are astonished when they see their methods returning nil unexpectedly.

Ruby skips items from list tasks

I am trying to make an app which if give the option to type, it types false then it skips the certain element from the list and it jumps to the next executing the same task.
That is the basic idea of the following code:
string["items"].each do |item|
p continue.to_s + "<- item"
begin
Anemone.crawl("http://" + item["displayLink"] + "/") do |anemone|
anemone.on_every_page do |page|
if continue.chomp.to_bool == false
raise "no more please"
end
request = Typhoeus::Request.new(page.url, followlocation: true)
response = request.run
email = /[-0-9a-zA-Z.+_]+#[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/.match(response.body)
if email.nil?
else
p email
begin
continue = Timeout::timeout(2) do
p "insert now false/nothing"
gets
end
rescue Timeout::Error
continue = "true"
end
end
end
end
rescue
continue = true
next
end
p "---------------------------------------------------------"
end
As the code shows, if the user types false when prompted the app should skip the item and go to the next one. However what it does is: when the user types false the app skips the current item and then doesn't execute any of the code that should be executed for all of the other items except the printing ( the second line of code );
Here is how the output looks like:
$ruby main.rb
"1"
"true<- item"
#<MatchData "support#keycreative.com">
"insert now false/nothing"
false
"true<- item"
"true<- item"
"true<- item"
As I'm doing my best to show after false is entered the code does skip the certain item from the list but it also never ever executes code for the other items as it should since it is an each loop
First I thought that maybe the continue is false however as you can see from the output the continue is true which makes me wonder why does ruby skip my code?
UPDATE
Here is where the to_bool method comes from:
class String
def to_bool()
return true if self == "true"
return false if self == "false"
return nil
end
end
In your last rescue statement add:
rescue => e
puts e.message
continue = true
next
end
and inspect the output. Most likely your code is throwing an exception other than "no more please" (I expect undefined method to_bool for true:TrueClass). Note that using exception for skipping the loop element is a terrible idea. Why can't you just get rid of this rescue and do:
if continue.chomp.to_bool == false
continue = true
next
end
There are a lot of things in this code which makes it very un-ruby-like. If you want to improve it please paste it to StackExchange CodeReview page. (link in the comment).
UPDATE:
My bad, you are in nested loop, so the if statement won't work. You might look at sth similar to raise/rescue bit, namely throw/catch, see example here: How to break from nested loops in Ruby?. I still think you should post it to codereview though for refactoring advises.
As to your actual code (without refactoring). You are calling to_bool method on continue, and in your rescue block you assign true instead of 'true'. Hence your to_bool method raises exception which is then rescued same way as 'no more please' exception.

Why isn't rspec memoizing this?

I have a test which is a bit like the following. The details isn't important, but I have a method which takes about 10 seconds, and gets back some data which I want to use a bunch of times in a bunch of tests. The data won't be any more fresh - I only need to fetch it once. My understanding of let is that it memoizes, so I would expect the following to only call slow_thing once. But I see it called as many times as I refer to slowthing. What am I doing wrong?
describe 'example' do
def slow_thing
puts "CALLING ME!"
sleep(100)
end
let(:slowthing) { slow_thing }
it 'does something slow' do
expect(slowthing).to be_true
end
it 'does another slow thing' do
expect(slowthing).to be_true
end
end
When I run the test, I see CALLING ME! as many times as I have assertions or use slowthing.
The documentation states values are not cached across examples:
The value will be cached across multiple calls in the same example but not across examples. [Emphasis mine.]
E.g., also from the docs:
$count = 0
describe "let" do
let(:count) { $count += 1 }
it "memoizes the value" do
count.should == 1
count.should == 1
end
it "is not cached across examples" do
count.should == 2
end
end
From https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

Is it possible for RSpec to expect change in two tables?

RSpec expect change:
it "should increment the count" do
expect{Foo.bar}.to change{Counter.count}.by 1
end
Is there a way to expect change in two tables?
expect{Foo.bar}.to change{Counter.count}.by 1
and change{AnotherCounter.count}.by 1
I prefer this syntax (rspec 3 or later):
it "should increment the counters" do
expect { Foo.bar }.to change { Counter, :count }.by(1).and \
change { AnotherCounter, :count }.by(1)
end
Yes, this are two assertions in one place, but because the block is executed just one time, it can speedup the tests.
EDIT: Added Backslash after the .and to avoid syntax error
I got syntax errors trying to use #MichaelJohnston's solution; this is the form that finally worked for me:
it "should increment the counters" do
expect { Foo.bar }.to change { Counter.count }.by(1)
.and change { AnotherCounter.count }.by(1)
end
I should mention I'm using ruby 2.2.2p95 - I don't know if this version has some subtle change in parsing that causes me to get errors, it doesn't appear that anyone else in this thread has had that problem.
This should be two tests. RSpec best practices call for one assertion per test.
describe "#bar" do
subject { lambda { Foo.bar } }
it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end
If you don't want to use the shorthand/context based approach suggested earlier, you can also do something like this but be warned it will run the expectation twice so it might not be appropriate for all tests.
it "should increment the count" do
expectation = expect { Foo.bar }
expectation.to change { Counter.count }.by 1
expectation.to change { AnotherCounter.count }.by 1
end
Georg Ladermann's syntax is nicer but it doesn't work. The way to test for multiple value changes is by combining the values in arrays. Else, only the last change assertion will decide on the test.
Here is how I do it:
it "should increment the counters" do
expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1])
end
This works perfectecly with the '.to' function.
The best way I've found is to do it "manually":
counters_before = Counter.count
another_counters_before = AnotherCounter.count
Foo.bar
expect(Counter.count).to eq (counters_before + 1)
expect(AnotherCounter.count).to eq (another_counters_before + 1)
Not the most elegant solution but it works
After none of the proposed solutions proved to actually work, I accomplished this by adding a change_multiple matcher. This will only work for RSpec 3, and not 2.*
module RSpec
module Matchers
def change_multiple(receiver=nil, message=nil, &block)
BuiltIn::ChangeMultiple.new(receiver, message, &block)
end
alias_matcher :a_block_changing_multiple, :change_multiple
alias_matcher :changing_multiple, :change_multiple
module BuiltIn
class ChangeMultiple < Change
private
def initialize(receiver=nil, message=nil, &block)
#change_details = ChangeMultipleDetails.new(receiver, message, &block)
end
end
class ChangeMultipleDetails < ChangeDetails
def actual_delta
#actual_after = [#actual_after].flatten
#actual_before = [#actual_before].flatten
#actual_after.map.with_index{|v, i| v - #actual_before[i]}
end
end
end
end
end
example of usage:
it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do
a = "." * 4
b = "." * 10
times_called = 0
expect {
times_called += 1
a += ".."
b += "-----"
}.to change_multiple{[a.length, b.length]}.by([2,5])
expect(times_called).to eq(1)
end
Making by_at_least and by_at_most work for change_multiple would require some additional work.
I'm ignoring the best practices for two reasons:
A set of my tests are regression tests, I want them to run fast, and
they break rarely. The advantage of having clarity about exactly
what is breaking isn't huge, and the slowdown of refactoring my code
so that it runs the same event multiple times is material to me.
I'm a bit lazy sometimes, and it's easier to not do that refactor
The way I'm doing this (when I need to do so) is to rely on the fact that my database starts empty, so I could then write:
foo.bar
expect(Counter.count).to eq(1)
expect(Anothercounter.count).to eq(1)
In some cases my database isn't empty, but I either know the before count, or I can explicitly test for the before count:
counter_before = Counter.count
another_counter_before = Anothercounter.count
foo.bar
expect(Counter.count - counter_before).to eq(1)
expect(Anothercounter.count - another_counter_before).to eq(1)
Finally, if you have a lot of objects to check (I sometimes do) you can do this as:
before_counts = {}
[Counter, Anothercounter].each do |classname|
before_counts[classname.name] = classname.count
end
foo.bar
[Counter, Anothercounter].each do |classname|
expect(classname.count - before_counts[classname.name]).to be > 0
end
If you have similar needs to me this will work, my only advice would be to do this with your eyes open - the other solutions proposed are more elegant but just have a couple of downsides in certain circumstances.

Resources