Skip to content

Integrating with Aruba In Process Runs

r edited this page Jun 16, 2017 · 9 revisions

Aruba is a cucumber library for writing BDD specs for command-line applications. By default it executes the given commands in a separate child process which is both slower, and does not provide the ability to easily stub or mock components. While BDD argues against significant stubbing and mocking, sometimes it is unavoidable if your Thor application depends on external services.

Aruba outlines the process for configuring your CLI app to handle dependency injection allowing same-process test runs however it is not immediately obvious how to integrate this with Thor. The key is to put a wrapper between your binary and your Thor class like so:

bin/mycli

#!/usr/bin/env ruby

require 'mycli/runner'

MyCli::Runner.new(ARGV.dup).execute!

lib/mycli/runner.rb

require 'mycli/app'

module MyCli
  class Runner
    # Allow everything fun to be injected from the outside while defaulting to normal implementations.
    def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
      @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
    end

    def execute!
      exit_code = begin
        # Thor accesses these streams directly rather than letting them be injected, so we replace them...
        $stderr = @stderr
        $stdin = @stdin
        $stdout = @stdout

        # Run our normal Thor app the way we know and love.
        MyCli::App.start(@argv)

        # Thor::Base#start does not have a return value, assume success if no exception is raised.
        0
      rescue StandardError => e
        # The ruby interpreter would pipe this to STDERR and exit 1 in the case of an unhandled exception
        b = e.backtrace
        @stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
        @stderr.puts(b.map{|s| "\tfrom #{s}"}.join("\n"))
        1
      rescue SystemExit => e
        e.status
      ensure
        # TODO: reset your app here, free up resources, etc.
        # Examples:
        # MyApp.logger.flush
        # MyApp.logger.close
        # MyApp.logger = nil
        #
        # MyApp.reset_singleton_instance_variables

        # ...then we put the streams back.
        $stderr = STDERR
        $stdin = STDIN
        $stdout = STDOUT
      end

      # Proxy our exit code back to the injected kernel.
      @kernel.exit(exit_code)
    end
  end
end

features/support/env.rb

# ...

require 'aruba/cucumber'
require 'aruba/in_process'
require 'mycli/runner'

Aruba::Processes::InProcess.main_class = MyCli::Runner
Aruba.process = Aruba::Processes::InProcess

# ...
Clone this wiki locally