A sketched picture of James' head

James Smith

Building a better future out of code

Mocking Kernel#require

Posted by

The other day, I remembered to add coverage analysis to my AMEE-Ruby tests, using rcov. The results were pretty good - most of the code was already tested, except a few failure cases, and I quickly wrote some new tests to make sure those were working properly. One little bit of code stood out though. Because AMEE talks XML and JSON, my gem can use JSON, but only if the JSON gem is available on the system (XML support is built into core Ruby). Problem is, require calls throw errors if they fail, so I had to write some code to handle the load failure and carry on regardless. This is the code inside amee.rb:

begin require 'json' rescue LoadError nil end

Problem with this is that the gem is installed on my system, so the require never fails, and the rescue is never used. Rcov notices, and I can't get to 100% coverage, which is annoying.

So, mocking to the rescue. We have to make Kernel#rescue throw an error if we try to require ‘json', even if the gem is installed. At first, I tried to use flexmock to do this, but couldn't make it work - if anyone knows how, please tell me. My final approach was to monkeypatch Kernel#require inside my test so that require ‘json' (and only json) would fail:

it "should cope if json gem isn't available" do # Monkeypatch Kernel#require to make sure that require 'json' # raises a LoadError module Kernel def require_with_mock(string) raise LoadError.new if string == 'json' require_without_mock(string) end alias_method :require_without_mock, :require alias_method :require, :require_with_mock end # Remove amee.rb from required file list so we can load it again $".delete_if{|x| x.include? 'amee.rb'} # Require file - require 'json' should throw a LoadError, # but we should cope with it OK. lambda { require 'amee' }.should_not raise_error end

We have to make a new require function, then use alias_method to rename the old one and install our new one in it's place. Rails includes alias_method_chain to make this easier, but that's not available in pure Ruby. Never mind. Once we've done the monkeypatch, we take amee.rb out of the $" array, which lists all the currently-required files, to make sure we can require it again, then simply run the require and make sure it catches the error that is thrown by our patched require. And the most satisfying part? The rescue is executed, the test works, and we get to 100% coverage. I've got a nice warm fuzzy feeling inside… mmmmmm.


Add a comment