Mocking Backticks and Other Kernel Methods
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:
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:
and
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:
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.).
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:
- runs the command
- prints out the output
- fails if the return code is bad
- fails if the output includes "ERROR" (useful when running database imports with sqlplus)
- and returns the output just to be a good citizen
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.).
Comments
I've been trying to mock require and now I'm stuck with RSpec built in mocking not having an equivalent of #any_instance.
Kernel.expects( :` ).with( 'unzip -d /tmp test.zip' )
Passed in my tests. This is Rails 2.1.