Mulan
Mulan

Reputation: 135187

How to mock an actual Object#method call using Mocha in Ruby?

Interacting directly with brains is not easy, so I have a little Gateway Pattern in use with some Dependency Inversion.

NumberCruncher is a wrapper for my Brain class.

class NumberCruncher

  def initialize brain = Brain.new
    @brain = brain    
  end

  def times_one_hundred *numbers
    numbers.map &@brain.method(:multiply_by_100)
  end

end

I'm getting an error when testing though:

NameError: undefined method `multiply_by_100' for class `Mocha::Mock'

Here's the test

class NumberCruncherTest

  def setup
    @brain = mock
    @cruncher = NumberCruncher.new @brain
  end

  def test_times_one_hundred
    @brain.expects(:multiply_by_100).with(1).returns(100)
    @brain.expects(:multiply_by_100).with(2).returns(200)
    @brain.expects(:multiply_by_100).with(3).returns(300)

    assert_equal [100, 200, 300], @cruncher.times_one_hundred(1,2,3)
  end

end

I'm assuming it's because of the &@brain.method(:multiply_by_100) call and mocha works by using method_missing or something. The only solution seems to be to change the setup

class NumberCruncherTest

  class FakeBrain
    def multiply_by_100; end
  end

  def setup
    @brain = FakeBrain.new
    @cruncher = NumberCruncher.new @brain
  end

  # ...
end

However, I think this solution kind of sucks. It gets messy fast and it putting tons of Fake* classes all over my tests. Is there any better way to do this with mocha?

Upvotes: 1

Views: 1245

Answers (1)

Ismael
Ismael

Reputation: 16710

I think you can fix your problem by changing your method.

from

numbers.map &@brain.method(:multiply_by_100)
# which is equivalent to (just to understand the rest of my answer)
numbers.map {|number| @brain.method(:multiply_by_100).to_proc.call(number) }

to

numbers.map {|number| @brain.send(:multiply_by_100, number) }

This is actually better because there are some issues with your code. Transforming an object method into a proc (as you are doing), kinda freezes the state of your object into the proc and so any changes on instance variables will not take effect, and probably it's slower. send should work fine on your case, and works with any mocking framework.

Btw, my guess on why your test does not work it's because mocha does not stub proc methods, and for good, because if you transform a method into a proc, you are not testing a method call anymore but a proc call.

And because everyone loves benchmarks:

@o = Object.new

def with_method_to_proc
  @o.method(:to_s).to_proc.call
end

def with_send
  @o.send(:to_s)
end

def bench(n)
  s=Time.new

  n.times { yield }

  e=Time.new
  e-s
end


bench(100) { with_method_to_proc }
# => 0.000252
bench(100) { with_send }
# => 0.000106


bench(1000) { with_method_to_proc }
# => 0.004398
bench(1000) { with_send }
# => 0.001402


bench(1000000) { with_method_to_proc }
# => 2.222132
bench(1000000) { with_send }
# => 0.686984

Upvotes: 0

Related Questions