Let's build a Ruby mocking framework! Sorta...kinda?

January 27, 2012

For whatever reason, I was thinking about mocking and stubbing the other day.

It’s pretty cool that testing librarys allow such fine-grain control of objects under test. It’s powerful to be able to write tests with functionality like:

stubbedObj.expect("foo").willReturn(123)
mockObj.expect("bar").withArgs("baz").toBeCalled(4).shouldReturn(null)

I got to thinking about how you would actually implement such a library. I wanted to explore some concepts of metaprogramming - but I didn’t want to see what something like NUnit or RSpec did under the hood. That would be cheating; I wanted to see if I could figure out an approach that might work on my own.

I sat down to try out some ideas and quickly discovered that trying to duplicate all the functionality of a mocking/stubbing framework is hard work. Certainly harder than I wanted to spend on a toy exercise on a Friday evening!

So I settled on trying to implement a basic Stub.

My idea:

  • Make a class StubbedFoo that derives from Foo
  • Intercept all methods called on StubbedFoo before they went to Foo
  • Return a canned response based on the method name
  • Allow method/response pairs to be added to StubbedFoo

Here’s the mostly-working toy implementation in Ruby (took me about 90 minutes):

I used Ruby’s undef to undefine the instance methods of the class under test on-the-fly. I then build up a @canned_responses hash that uses the method name (as a symbol) for the key.

When you try to call a method (like Statistics#compute_average) it hits the stub’s method_missing (since that method is now Undefined). The stub simply returns the canned value from the hash or raises an exception if there is no response setup.

I tried to figure out how to structure the code so I could just mix it into a class, instead of deriving directly - but I couldn’t get it working. Any tips would be appreciated!

I’ve only scratched the surface of what you can do with metaprogramming but I think this was a worthwhile exercise. I learned some new Ruby tricks and challenged myself to think about how code I take for granted is actually implemented.


built with , Jekyll, and GitHub Pages — read the fine print