How to return a dynamic value from a Mocha mock in Ruby
Asked Answered
E

4

16

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
Echols answered 30/4, 2010 at 5:56 Comment(0)
B
5

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.

Baton answered 23/7, 2010 at 5:12 Comment(1)
This is not the intended use of Expectation#yields. I would not recommend this technique.Lelia
L
4

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.

Lelia answered 26/9, 2010 at 16:43 Comment(0)
A
4

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)
Absalom answered 11/10, 2015 at 17:5 Comment(1)
Perfect ! You saved me a ton ! Thanks !Onia
Z
0

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."
Zizith answered 20/5, 2010 at 14:42 Comment(2)
I think this answer is a little misleading. Although it is correct in suggesting that you can use Expectation#with to specify different return values for different parameter values, the use of Procs is unnecessary in specifying the return values.Lelia
True, the Proc isn't needed. I could have specified the return values as strings, directly. I used a Proc in my example code to more closely mirror the question.Zizith

© 2022 - 2024 — McMap. All rights reserved.