How to test a function which takes a block with rspec - ruby

I have a function, which accepts a block, opens a file, yields and returns:
def start &block
.....do some stuff
File.open("filename", "w") do |f|
f.write("something")
....do some more stuff
yield
end
end
I am trying to write a test for it using rspec. How do I stub File.open so that it passed an object f (supplied by me) to the block instead of trying to open an actual file? Something like:
it "should test something" do
myobject = double("File", {'write' => true})
File.should_receive(:open).with(&blk) do |myobject|
f.should_receive(:write)
blk.should_receive(:yield) (or somethig like that)
end
end

I think what you're looking for are yield matchers, i.e:
it "should test something" do
# just an example
expect { |b| my_object.start(&b) }.to yield_with_no_args
end

Your other choice is to stub :open with a new object of File, as such:
file = File.new
allow(File).to receive(:open) { file }
file.each { |section| expect(section).to receive(:write) }
# run your method

Related

rspec yield block, but call original

So I have the following:
foo.each do |f|
f.begin
do_stuff
do_more_stuff
end
end
And I mock the f object with an and_yield() call. I want to be able to test the begin method by passing it the original block { do_stuff do_more_stuff }, not a mock implementation.... I cant just let the begin method be called on the mock without at least stubbing it, so what do I do?
Again, an undocumented feature that i found:
allow(thing).to receive(:foo) do |_, &block|
block.call
end
le sigh....
The following worked for me:
original = thing.method(:foo)
expect(thing).to receive(:foo) do |_params|
# check params
expect(params).to include(something)
# then
original.call(params)
end

How can I pass a block to an `instance_eval`-ed block?

I want to pass a block to a block that is instance_eval-ed like this,
instance_eval(&block) { puts "test" }
where block is defined as containing something like:
puts "Incoming message:"
yield
Is this possible? I discovered a way of doing this with fibers, but I'm trying to use yield first. Looking at this question, it looks like this might not be possible, but I wanted to confirm.
It's weird, indeed. Why instance_eval ? It's usually used to change self, and evaluate in the context of the receiver.
cat = String.new('weird cat')
block1 = lambda do |obj, block|
puts "Incoming message for #{obj}:"
block.call
end
block2 = Proc.new { puts "test" }
block3 = lambda {|obj| block1.call(obj, block2)}
cat.instance_eval(&block3)
Execution (Ruby 1.9.2) :
$ ruby -w t2.rb
Incoming message for weird cat:
test

Ruby yield newbie issue

I have recently started using Ruby, so quite new to this. My current objective is to use a ruby module called retort, my problem is that I don't understand the configure method which is looking like this:
def configure
config = Config.new
yield config
##service = XMLRPC::Client.new2(config.url)
end
Config class is simple and looks like:
class Config
attr_accessor :url
end
I tried to create a small example to play around in order to understand how exactly that is supposed to work:
class TestClass
def test_method
config = String.new
yield config
p config
end
end
d = TestClass.new
d.test_method { 'test string' }
Of course it doesn't return 'test string' but an empty string.
Thank you for any help :)
Can you be clearer about what's confusing you? Does this code make sense to you?
class TestClass
def test_method
config = yield
p config
end
end
d.test_method { "test string" }
The yield statement invokes the block. The block returns a string, which is assigned to the config variable back in the test_method and is then printed. Does that make it clearer?
In your code, the line yield config is invoking the block while passing in the just-instantiated Config object. For instance:
def foo
s = "a string"
yield s
p "In foo printing " + s
end
foo { |x| p "In block printing " + x }

Using ruby blocks with method calls

The following code works perfectly.
#doc = open(link) { |f| Hpricot(f) }
But I want to use the following code, which doesn't seem to play well with the Hpricot block (e.g. #doc is a TempFile object, not a Hpricot document object)
#doc = resolve_link(link) { |f| Hpricot(f) }
def resolve_link(link)
begin
return open(link)
rescue
logger.debug("#{$!} for link #{link}")
raise Exceptions::ErrorResolvingLink.new("Cannot resolve link #{link}.")
end
end
Any idea how I can get the second version of the code to work?
You're calling resolve_link with a block but you're not passing that block down to open. Try this instead:
def resolve_link(link, &block)
begin
return open(link, &block)
#...
You have to use yield to invoke the block.
See this answer for a very simple example:
Blocks and yields in Ruby
So something along the lines
def resolve_link(link)
...
yield ( some_value_to_pass_to_the_block )
...
end
Should work.

How to return a dynamic value from a Mocha mock in Ruby

The gist of my problem is as follows:-
I'm writing a Mocha mock in Ruby for the method represented as "post_to_embassy" below. It is not really our concern, for the purpose of describing the problem, what the actual method does. But I need the mock to return a dynamic value. The proc '&prc' below is executing rightly in place of the actual method. But the "with" method in Mocha only allows for boolean values to be returned. So the code below outputs nil. I need it to output the value being passed through orderInfoXml. Does anyone know of an alternate method I can use?
require 'rubygems'
require 'mocha'
include Mocha::API
class EmbassyInterface
def post_to_embassy(xml)
puts "This is from the original class:-"
puts xml
return xml
end
end
orderInfoXml = "I am THE XML"
mock = EmbassyInterface.new
prc = Proc.new do |orderXml|
puts "This is from the mocked proc:-"
puts orderXml
orderXml
end
mock.stubs(:post_to_embassy).with(&prc)
mock_result = mock.post_to_embassy(orderInfoXml)
p mock_result
#p prc.call("asd")
output:-
This is from the mocked proc:-
I am THE XML
nil
I'm not sure if there is a perfect method for this. But to make life easier, than to stubbing each possible response (as described in another answer), you could go with Mocha's yields method.
require "rubygems"
require "mocha"
include Mocha::API
class EmbassyInterface
def post_to_embassy(xml)
puts "This is form the original class:-"
puts xml
xml
end
end
order_info_xml = "I am the xml"
mock = EmbassyInterface.new
prc = Proc.new do |order_xml|
puts "This is from the mocked proc:-"
puts order_xml
order_xml
end
mock.stubs(:post_to_embassy).yields prc
prc_return = nil
mock.post_to_embassy { |value| prc_return = value.call("hello world") }
puts prc_return
mock.post_to_embassy { |value| prc_return = value.call("foo") }
puts prc_return
outputs:
This is from the mocked proc:-
hello world
hello world
This is from the mocked proc:-
foo
foo
This will require you to assign the return of your prc, and it's not exactly pretty (imo). But, you don't have to stub out each expectation, which will give you quite a bit of freedom.
In general, you are normally better off specifying explicit return values in tests. It tends to make tests hard to understand and hard to maintain if you introduce separate logic into determining what value to return.
I would suggest that you either use Expectation#with with suitable ParameterMatchers to explicitly define return values for different parameter values or use the StateMachine functionality.
Mocha doesn't appear to support this. Add this to your test_helper.rb:
# Replace klass's method_name with method_implementation
def stub_replace(klass, method_name, &method_implementation)
klass.singleton_class.send(:alias_method, "#{method_name}_mock_backup", method_name)
klass.define_singleton_method(method_name, method_implementation)
end
def undo_stub_replace(klass, method_name)
klass.singleton_class.send(:alias_method, method_name, "#{method_name}_mock_backup")
end
Then replace the last 4 lines of your test with:
stub_replace(EmbassyInterface, :post_to_embassy, &prc)
mock_result = mock.post_to_embassy(orderInfoXml)
p mock_result
# cleanup
undo_stub_replace(EmbassyInterface, :post_to_embassy)
I haven't found a way to make the output of a mocked method completely dynamic, but if you have a limited, known number of inputs you can get the output to work correctly.
require 'rubygems'
require 'mocha'
include Mocha::API
class EmbassyInterface
def post_to_embassy(xml)
"original: #{xml}"
end
end
to_mock = EmbassyInterface.new
orderInfoXml1 = "I am the first XML."
orderInfoXml2 = "I am the second XML."
p to_mock.post_to_embassy(orderInfoXml1)
prc = Proc.new do |xml|
"mocked: #{xml}"
end
to_mock.stubs(:post_to_embassy).with(orderInfoXml1).returns(prc.call(orderInfoXml1))
to_mock.stubs(:post_to_embassy).with(orderInfoXml2).returns(prc.call(orderInfoXml2))
p to_mock.post_to_embassy(orderInfoXml1)
p to_mock.post_to_embassy(orderInfoXml2)
p to_mock.post_to_embassy(orderInfoXml1)
output:
"original: I am the first XML."
"mocked: I am the first XML."
"mocked: I am the second XML."
"mocked: I am the first XML."

Resources