1

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?

Vega
  • 27,856
  • 27
  • 95
  • 103
Mulan
  • 129,518
  • 31
  • 228
  • 259

1 Answers1

0

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
Ismael Abreu
  • 16,443
  • 6
  • 61
  • 75
  • The reason i used `&method` was for writing less code. If I was going to expand this, I would probably use `numbers.map { |n| @brain.multiply_by_100 n }` instead of `method.to_proc` or `send`... My point was that I don't want to have to change the way my `Number` class is written. I feel like Mocha should allow me to use any pattern within my implementation. – Mulan Jul 16 '13 at 19:33
  • Also, please provide your version of Ruby when offering a benchmark. – Mulan Jul 16 '13 at 19:34
  • Not that this helps me at all, but I think this is a more relevant benchmark: https://gist.github.com/naomik/6012203 – Mulan Jul 16 '13 at 20:10
  • yeah, you benchmark is better I guess. If you think mocha should have that feature you should file an issue. – Ismael Abreu Jul 17 '13 at 01:28