Temporarily Redefine (not stub) Methods for Testing


Today I came across a case where I wanted to temporarily redefine a method’s implementation for purposes of testing. Specifically I want to test with a condition of a large number of records in a database. Since I don’t actually want to load a large number of records into the database I just want to make the system think there is a large number of records for the purposes of the test.

Here’s a gist showing what I ended up doing:

module Count
  def fake_count
    words.count + 500
  end
end

class Thing
  def words
    %w(one two three)
  end
  def count
    words.count
  end
end

def assert(value)
  raise "Assertion failed" unless value == true
end

thing = Thing.new
assert thing.count == 3
puts thing.count

class Thing
  include Count
  alias_method :real_count, :count
  alias_method :count, :fake_count
end

thing = Thing.new
assert thing.count == 503
puts thing.count

class Thing
  alias_method :count, :real_count
end

thing = Thing.new
assert thing.count == 3
puts thing.count
view raw test.rb This Gist brought to you by GitHub.
  1. Create a module that has the faked version of the method
  2. Open up the original class and include the module (I want it on all instances – if it were just one I’d use extend)
  3. While the class is open call alias_method :real_count, :count which creates an alias of real_count that points to the count method
  4. While still in the open class, call alias_method :count, :fake_count which creates an alias of count to the fake_count method. Note that real_count still points to the original implementation

Once the class has been opened and modified it will return the new value. To undo the change I can just open up the class again and call alias_method :count, :real_count which makes count behave as it did originally.

Why not stub? In this case I wanted to use a method in Thing and just add 500 to it. I probably could have also called real_count + 500 which would have effectively decorated the original count method.

Can you think of alternative approaches? Let me know in the comments.

  1. #1 by Chris Morris on July 19, 2011 - 8:57 am

    Another option: you could subclass Thing with FakeLargeThing, and just use FakeLargeThing in the one test. This way you wouldn’t have to modify Thing at all or worry about restoring the aliased methods.

    class Thing
    def words
    %w(one two three)
    end
    def count
    words.count
    end
    end

    class FakeLargeThing < Thing
    def count
    super + 500
    end
    end

    def assert(value)
    raise "Assertion failed" unless value == true
    end

    thing = Thing.new
    assert thing.count == 3
    puts thing.count

    thing = FakeLargeThing.new
    assert thing.count == 503
    puts thing.count

    thing = Thing.new
    assert thing.count == 3
    puts thing.count

(will not be published)