Stubbing in Ruby
by Stephen Pike

Ruby is a particularly fun language thanks to metaprogramming. We’re allowed huge flexibility in re-wiring core pieces of a program even while it’s running. This power can be abused, but one place in which it’s a huge positive is testing.

“Testing” is a huge field full of strongly held philosophies, so let’s focus on the specific problem of unit testing: verifying that one particular piece of a program works as intended. Unit testing involves isolating precisely what behavior a component is responsible for and ensuring that it behaves as expected when the rest of the system is working correctly.

We’re allowed to assume that the rest of our system works, so wouldn’t it be nice if we could guarantee that? Stubbing lets us do this. With stubbing, we replace calls to external modules with a pre-defined result. Our tests no longer rely on external code and they’ll run faster as complex functions are replaced with pre-defined results.

For example, let’s say we’re building a system involving credit card payments. Our system has two classes, Person and CreditCard:

class CreditCard
  def process
    ... # complicated finance stuff
    return successful_charge ? true : false
  end
end
class Customer
  # Charge, sending invoice on success or a notice on failure
  def charge_and_notify
    if @credit_card.process
      send_invoice
    else
      send_error_notice
    end
  end
end

We’d like to write unit tests for Customer#charge_and_notify, ensuring that we send the right email based on whether a charge succeeds. We don’t want to actually charge cards during testing, though, so how do we test this method? Let’s stub the CreditCard#process function to make testing easier.

Common test libraries like minitest have built in support for stubbing (use those if you’re actually doing this). For now, though, let’s remove the magic and write it ourselves. We open up Object and add a class method called stub, which takes a method name and desired result and ensures than any call to that method returns that result:

class Object
  def self.stub(method, result)
    # Store the existing method first
    alias_method "_custom_stubbed_#{method}", method
    define_method method do |*args|
      result
    end
  end
end

This is straightforward in Ruby. First we store the old method in a safe place with the name _custom_stubbed_#{old_name}. Then we overwrite the method to return the passed in result. Now we can do this:

  > CreditCard.stub(:process, true)
  > person = Person.new(card)
  > person.charge_and_notify # sends a success email!

When process is called on card, it will use our new method and return true. That’s great, but we’re not done yet.

We’ve now overwritten process for all Credit cards for the entire life of our ruby process. If we have another test which creates a second person and credit card, that card will also return true for every process call. It’d be better if we could limit the scope of the stubbing in some way. Let’s improve stub to accept a block, and only apply the stub during the block:

class Object
  def self.stub(method, result, &block)
    # Store the existing method
    new_name = "_custom_stubbed_#{method}"
    alias_method new_name, method

    define_method method do |*args|
      result
    end

    if block
      begin
        yield
      ensure # Restore the old method outside of the block
        alias_method method, new_name
      end
    end
  end
end

If we’re given a block, we’ll run it and then restore the old method once we’re done.

Now we can do this:

  > CreditCard.stub(:process, true) do
  >   person = Person.new(card)
  >   person.charge_and_notify # `CreditCard#process` stubbed out
  > end
  > person.charge_and_notify # `CreditCard#process` back to normal

This is pretty good, but let’s add one more piece of flexibility. What if we wanted to stub process differently for different instances of CreditCard? Let’s imaging we allow a person to link multiple credit cards to their account, and the charge_and_notify function should check them all. It’d be great to do this:

  >   card1.stub(:process, false) # invalid card
  >   card2.stub(:process, true) # valid card
  >   person.cards = [card1, card2]
  >   person.charge_and_notify # success, since card2 works

In a prototype language like Javascript we’d just create new methods directly on our card1 and card2 objects and everything would work. In Ruby, we have to define these instance-specific methods using define_singleton_method:

class Object
  def stub(method_name, result, &block)
    define_singleton_method method_name do |*args|
      result
    end
  end
end

Now calling card1.stub(:process, true) only affects card1. We can still stub methods across all instances using CreditCard.stub from before.

For completeness, let’s add block acceptance to our new instance-level stubbing. This will let us do card1.stub(:process, true) { ... applies here ... }:

class Object
  def instance_stub(method_name, result, &block)
    old_method = method(method_name)
    define_singleton_method method_name do |*args|
      result
    end
    if block
      yield
      define_singleton_method method_name, old_method
    end
  end
end

All this stubbing code along with tests and some documentation are available in a github repo. While you shouldn’t use my library in any production code, it serves as a good example of how simple Ruby’s metaprogramming makes seemingly complicated features.