Mocking Backticks and Other Kernel Methods

15 Nov 2007

There’s a bunch of places in our build where we need to execute a system command (such as running sqlplus) but often times we’ve found that the command fails and our build happily churns away. It’s pretty easy to check the result of system command with $?.success? but we have to remember to do that everywhere… So that means it’s time to extract a method. Here’s what Kurtis and I came up with:

  module DatabaseHelper
   def self.command_runner(command)
     output = `#{command}`
     puts output
     fail unless $?.success?
     fail if output.include? "ERROR"
     output
   end
  end

So you pass in a command as a string and the command runner:

So that’s cool but when I started to write the module I thought “Hey, I can test this.” Which is a cool side effect of extracting methods from your rake file. So cool that you probably want to consider it even if the method won’t be re-used.

Anyway, I started to write the test and realized that I would need to mock the backtick, which is a Kernel method. I’m using mocha so I tried:

Kernel.expects(:`)

and

Kernel.any_instance.expects(:`)

But no luck. Turns out you need to mock the class in which the backtick method will be called. So here’s the tests I ended up with:

unit_tests do

 test "command runner executes a command" do
   DatabaseHelper.expects(:`).returns("some output")
   $?.expects(:success?).returns(true)

   output = DatabaseHelper.command_runner("command")
   assert_equal "some output", output
 end

 test "command runner fails if ERROR in output" do
   DatabaseHelper.expects(:`).returns("ERROR: this is broken")
   $?.expects(:success?).returns(true)

   assert_raises(RuntimeError) do
     DatabaseHelper.command_runner("command")
   end
 end

 test "command runner fails if process fails" do
   DatabaseHelper.expects(:`)
   $?.expects(:success?).returns(false)

   assert_raises(RuntimeError) do
     DatabaseHelper.command_runner("command")
   end
 end
end

I was having such a good time mocking that I later went back and mocked out the puts so I didn’t have to see the output when running the tests. Gotta keep things clean.

Btw, you may notice that I’m not using Rspec a work anymore. Yep, it’s true. My new team is using DUST (well, the code that would later be part of the inspiration for DUST) which is pretty cool. I still prefer Rspec, but I can’t really make the case that we should switch to Rspec mid-stream (if we were using straight Rails testing, I would make that case.).