A Little Bit of Magic

One of Ruby’s strengths as a language is that it allows a great deal of metaprogramming; that is, Ruby programs can modify themselves as they execute. Testing is a common place to see metaprogramming. Recently, I’ve been using Minitest, which is a testing library for Ruby. Minitest lets you mock methods fairly easily, but the way the syntax works feels pretty magical. I don’t like magic (at least not in software), so I wanted to try to dig into this metaprogramming a bit and understand how mocks work. Note, this post is not about when, or even whether, we should mock stuff in Ruby; I just want to understand how the testing libraries accomplish mocking, and specifically how they accomplish stubbing.

We’ll do three things: stub a method on an object, stub a method on a class, and then stub a method on any instance of a class:

It’s sort of remarkable how little code there is. Ruby knows about Ruby, so we can tell Ruby that we wish Ruby was different, and it will just work.

One piece of code that surprised me is the rescue blocks in stub_any_instance. These are necessary because Ruby can freeze some objects, and they will raise a RuntimeError if you try to modify them. Rescuing TypeError exists because I was seeing type errors when I tried to send define_singleton_method to the class Complex. I didn’t know much about that class, so I did some digging. Since Ruby knows Ruby, I can just ask Ruby what public methods are available on the class Complex:

ary = Complex.public_methods - Object.public_methods
=> [:rectangular, :rect, :polar]
a = Complex(2,1)
=> (2+1i)

So it looks like Complex is a math class, presumably representing complex numbers. So now I have a bit of a riddle. Why can’t I define instance methods on Complex? I tried doing it directly, with a.send(:define_singleton_method, :print42, proc { puts 42 }) but that doesn’t work either; it also raises a TypeError. Is this something weird with numerics? It looks like it also doesn’t work for integers. However, I can manually add the method to the Fixnum class:

irb(main):016:0>b.send(:define_singleton_method, :print42, proc { puts 42 })
TypeError: can't define singleton

irb(main):021:0> class Fixnum
irb(main):022:1> def print42 ; puts 42 ; end
irb(main):023:1> end
=> nil
irb(main):024:0> b.print42
42

It looks like numeric types, and also Symbols, are not really references in Ruby, but are “immediate values.” So I can’t define a method on them because there’s no object reference to put the method on.

I’m not sure I’ll ever write code that looks much like this for production, but at least the testing libraries I’m using seem a lot less magical now.

Till next time, happy learning!

-Will

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s