4

I'm a Java-developer toying with Ruby, and loving it. I have understood that because of Ruby's metaprogramming facilities my unit-tests become much cleaner and I don't need nasty mocking frameworks. I have a class which needs the File class's services and in my test I don't want to touch my real filesystem. In Java I would use some virtual file system for easier "seams" to pass fake-objects in but in Ruby that's obviously overkill. What I come up seems already really nice compared to the Java-world. In my class under test I have an optional constructor parameter:

def initialize(file_class=File)

When I need to open files within my class, I can then do this:

@file_class.open(filename)

And the call goes to either the real File-class, or in case of my unit-test, it goes to a fake-class which doesn't touch the filesystem. I know there must be a better way to do this with metaprogramming?

Machavity
  • 30,841
  • 27
  • 92
  • 100
auramo
  • 13,167
  • 13
  • 66
  • 88

3 Answers3

12

Mocha (http://mocha.rubyforge.org/) is a very good mocking library for ruby. Depending on what you're actually wanting to test (i.e. if you want to just fake out the File.new call to avoid the file system dependency or if you want to verify that the correct arguments are passed into File.new) you could do something like this:


require 'mocha'

mock_file_obj = mock("My Mock File") do
  stubs(:some_instance_method).returns("foo")
end

File.stubs(:new).with(is_a(String)).returns(mock_file_obj)

Brian Phillips
  • 12,693
  • 3
  • 29
  • 26
  • Yes, I'd just rather not use a mocking library at all. Do you really need them in Ruby?. – auramo Sep 16 '08 at 13:43
  • 4
    I don't see any need to have an aversion to a mocking library. Mocha is just doing the metaprogramming for you (i.e. overriding methods, etc) that you're asking about doing manually. – Brian Phillips Sep 16 '08 at 13:46
  • @BrianPhillips How do you verify that the stubbed methods were called – Ankit Dhingra Sep 18 '13 at 14:14
  • 1
    @AnkitDhingra Mocha supports more fine grained expectations. if you use the 'expects' method instead of 'stubs', then after the test runs it will throw an error if the mocked method was not called. – Kevin Oct 06 '14 at 01:51
1

This is a particularly difficult challenge for me. With the help I received on this question, and some extra work on my behalf, here's the solution I arrived at.

# lib/real_thing.rb
class RealThing
  def initialize a, b, c
    # ...
  end
end

# test/test_real_thing.rb
class TestRealThing < MiniTest::Unit::TestCase

  class Fake < RealThing; end

  def test_real_thing_initializer
    fake = mock()
    Fake.expects(:new).with(1, 2, 3).returns(fake)
    assert_equal fake, Fake.new(1, 2, 3)
  end

end
Community
  • 1
  • 1
Mulan
  • 129,518
  • 31
  • 228
  • 259
1

In the case you've outlined, I'd suggest that what you're doing seems fine. I know that it's a technique that James Mead (the author of Mocha) has advocated. There's no need to do metaprogramming just for the sake of it. Here's what James has to say about it (and a long list of other techniques you could try)

tomafro
  • 5,788
  • 3
  • 26
  • 22