haiku code

Testing Thor Apps with Cucumber

July 08, 2013

Imagine you’re writing a command-line Ruby tool using Thor. Although probably if you’re reading this, you really are. Probably you would like to have nice test coverage with a bunch of integration tests. You might even want to write them in Cucumber (God save your soul!) for some reason. You’d google around and find Aruba - a Cucumber extension for testing command-line applications. You’d write some tests and it would work. Cool, huh?

The story could end here, but if you care about the speed of your tests, you need to go deeper. By default, Aruba runs your commands in a separate process, loading the whole command-line apps separately. Isolation is nice, but when you have 50+ tests you might want to omit the step of spawning a new process and loading the app. That’s how you’d do it:

# aruba_extension.rb
module MyFancyTool
  module TestSupport
    module ArubaExt
      def with_redirected_stdout(&block)
        redirect_stdout
        yield
        bring_back_stdout
      end

      def mock_stdout
        unescape @stdout_cache
      end

      def mock_stderr
        unescape @stderr_cache
      end

      def mock_output
        mock_stdout + mock_stderr
      end

      private

      def redirect_stdout
        @stdout_cache = ''
        @stderr_cache = ''
        @stdout_redirected = true
        @orig_stdout = $stdout
        @orig_stderr = $stderr
        $stdout = @mock_stdout = StringIO.new
        $stderr = @mock_stderr = StringIO.new
      end

      def bring_back_stdout
        @stdout_cache = @mock_stderr.string
        @stderr_cache = @mock_stdout.string
        @stdout_redirected = false
        $stdout = @orig_stdout
        $stderr = @orig_stderr
        @orig_stdout = @mock_stdout = nil
        @orig_stderr = @mock_stderr = nil
      end
    end
  end
end

World(MyFancyTool::TestSupport::ArubaExt)


# basic_steps.rb
When /^I run my fancy tool with command "(.*)"$/ do |args|
  args = args.split
  args[0] = args[0].to_sym
  Dir.chdir(current_dir) do
    with_redirected_stdout do
      MyFancyTool::MyCLI.start args
    end
  end
end

Then /^the last output should match (#{PATTERN})$/ do |expected|
  assert { mock_output =~ expected }
end


# example.feature
Feature: Reassuring awesomeness of MyFancyTool.
Scenario: Running the tool
When I run my fancy tool with command "do stuff"
Then the last output should match /awesome output/

You see that we run Ruby command-line commands in the Aruba process itself by calling MyCLI.start by hand and capturing standard output. Then we can match stuff against saved cache of the output to see if it’s what we expected it to be.

In my case it reduced the time to run all the features by a whopping 60%.

That’s all folks, stay covered!


Written by Wojciech Ogrodowczyk who takes photos, climbs mountains, and runs Brains & Beards to help companies deliver better mobile applications faster.

© 2012 - 2024, built in a 🚐 with Gatsby and ☀️