auramo
auramo

Reputation: 13357

Mocking constructors in Ruby

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?

Upvotes: 5

Views: 5940

Answers (3)

Mulan
Mulan

Reputation: 135415

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

Upvotes: 2

tomafro
tomafro

Reputation: 5846

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)

Upvotes: 1

Brian Phillips
Brian Phillips

Reputation: 13053

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)

Upvotes: 12

Related Questions