Fri, 30 Jun 06

Very very lightweight mocking(ish)

James has been developing a great mocking framework at work which makes life real easy when it comes to testing interaction between objects.

I’ve spent a couple of hours this week re-implementing Fred Stutzmans Micro Id Verifier in ruby (I’ll post it soon, just some database persistence to add). The job of the validator is to connect to a given url and compare any micro_id found on that page with an expected micro_id supplied by the user. All of my development has been off-line yet I needed a way to test that my objects were doing their job (i.e. connecting to the Internet). It’s actually irrelevant that I was off-line at the time, I’d still need a repeatable way of ensuring my objects are doing their jobs.

I’m using Net::HTTP to look after grabbing the html for a given page and am certainly not worried about testing the workings of that library. What I wanted to know, however, was that my client was utilising Net::HTTP in the way expected. I didn’t have access to the mocking framework used at work so I fell back to a very lightweight method that we’ve used previously.

The idea is to override the methods of an object that you expect to be called. When the overridden method is called, we add the message to a log for later inspection.

The implementation I have at the moment comprises four methods, although we could quite easily do away with ‘stub_instance_method’ (it just adds some safety checking to ‘define_instance_method’).

class Object

  def metaclass
    class << self; self; end
  end

  def define_instance_method(sym, &block)
    metaclass.__send__(:define_method, sym, &block)
  end

  def stub_instance_method(sym, &block)
    raise "#{self} does not respond to <#{sym}> and therefore cannot be stubbed" unless self.respond_to?(sym)
    define_instance_method(sym, &block)
  end

  def __log__
    @__log__ ||= []
  end

end

A (partially adapted and potentially non-working) example of this code being used in my tests is as follows.

def test_should_use_net_http_to_get_html_content
  url = 'http://localhost/page.htm'
  net_http = Object.new
  net_http.define_instance_method(:get) { |path| __log__ << "get(#{path})" }
  http_client = HttpClient.new
  http_client.get_html(url, net_http)

  assert_equal ['get(/page.htm)'], net_http.__log__
end

I define a ‘mock’ Net::HTTP object (net_http) that records a message when it gets sent the ‘get’ message. I can then check the log to ensure that the expected method was called; and be (hopefully) safe in the knowledge that when I supply an actual Net::HTTP object the code does what I expect.