Module: Toys::Testing

Defined in:
lib/toys/testing.rb

Overview

Helpers for writing tool tests.

EXPERIMENTAL: Interfaces are subject to change.

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#toys_cliToys::CLI

Returns the Toys CLI for this test class. By default, a single CLI and Loader are shared by all tests in a given class (or describe block).

Returns:



18
19
20
# File 'lib/toys/testing.rb', line 18

def toys_cli
  self.class.toys_cli
end

#toys_exec_tool(cmd, cli: nil, **opts) {|controller| ... } ⇒ Toys::Utils::Exec::Result Also known as: exec_tool

Runs the tool corresponding to the given command line, in a separate forked process, and returns a Utils::Exec::Result. You can either provide a block to control the process, or simply let it run and capture its output.

By default, a single CLI is shared among the tests in each test class or describe block. Thus, tools are loaded only once, and the loader is shared across the tests. If you need to isolate loading for a test, create a separate CLI and pass it in using the :cli keyword argument.

All other keyword arguments are the same as those defined by the Utils::Exec class. If a block is given, all streams are directed to a Utils::Exec::Controller which is yielded to the block. If no block is given, the output and error streams are captured and the input stream is closed.

This method uses "fork" to isolate the run of the tool. It will not work on environments such as JRuby or Ruby on Windows that do not support process forking.

Examples:

# Given the following tool:

tool "hello" do
  flag :shout
  def run
    puts message
  end
  def message
    shout ? "HELLO" : "hello"
  end
end

# You can test the tool's output as follows:

class MyTest < Minitest::Test
  include Toys::Testing
  def test_output_without_shout
    result = toys_exec_tool(["hello"])
    assert_equal("hello\n", result.captured_out)
  end
  def test_with_shout
    result = toys_exec_tool(["hello", "--shout"])
    assert_equal("HELLO\n", result.captured_out)
  end
end

Parameters:

  • cmd (String, Array<String>)

    The command to execute.

  • opts (keywords)

    The command options.

Yield Parameters:

Returns:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/toys/testing.rb', line 207

def toys_exec_tool(cmd, cli: nil, **opts, &block)
  cli ||= toys_cli
  cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
  opts =
    if block
      {
        out: :controller,
        err: :controller,
        in: :controller,
      }.merge(opts)
    else
      {
        out: :capture,
        err: :capture,
        in: :close,
      }.merge(opts)
    end
  cli.loader.lookup(cmd)
  tool_caller = proc { ::Kernel.exit(cli.runner.run(cmd)) }
  self.class.toys_exec.exec_proc(tool_caller, **opts, &block)
end

#toys_load_tool(cmd, cli: nil, verbosity: 0) {|tool| ... } ⇒ Object

Prepares the tool corresponding to the given command line, but instead of running it, yields the execution context to the given block. This can be used to test individual methods in a tool.

By default, a single CLI is shared among the tests in each test class or describe block. Thus, tools are loaded only once, and the loader is shared across the tests. If you need to isolate loading for a test, create a separate CLI and pass it in using the :cli keyword argument.

Note: this method runs the given block in-process. This means you can test assertions within the block, but any input or output performed by the tool's methods that you call, will manifest during your test. If this is a problem, you might consider redirecting the standard streams when calling this method, for example by using capture_subprocess_io.

Examples:

# Given the following tool:

tool "hello" do
  flag :shout
  def run
    puts message
  end
  def message
    shout ? "HELLO" : "hello"
  end
end

# You can test the `message` method in isolation as follows:

class MyTest < Minitest::Test
  include Toys::Testing
  def test_message_without_shout
    toys_load_tool(["hello"]) do |tool|
      assert_equal("hello", tool.message)
    end
  end
  def test_message_with_shout
    toys_load_tool(["hello", "--shout"]) do |tool|
      assert_equal("HELLO", tool.message)
    end
  end
end

Parameters:

  • cmd (String, Array<String>)

    The command to execute.

  • cli (Toys::CLI) (defaults to: nil)

    The CLI to load the tool from. Optional.

  • verbosity (Integer) (defaults to: 0)

    Initial verbosity. Default is 0.

Yield Parameters:

Returns:

  • (Object)

    The value returned from the block.



74
75
76
77
78
# File 'lib/toys/testing.rb', line 74

def toys_load_tool(cmd, cli: nil, verbosity: 0, &block)
  cli ||= toys_cli
  cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
  cli.load_tool(*cmd, verbosity: verbosity, &block)
end

#toys_run_tool(cmd, cli: nil, verbosity: 0, wrap_errors: true, handle_errors: true) ⇒ Integer

Runs the tool corresponding to the given command line, in-process, and returns the result code.

By default, a single CLI is shared among the tests in each test class or describe block. Thus, tools are loaded only once, and the loader is shared across the tests. If you need to isolate loading for a test, create a separate CLI and pass it in using the :cli keyword argument.

Note: This method runs the tool in-process. This is often faster than running it in a separate process with #toys_exec_tool, but it also means any input or output performed by the tool, will manifest during your test. If this is a problem, you might consider redirecting the standard streams when calling this method, for example by using capture_subprocess_io.

By default, an error raised by the tool is reported the way the toys executable would report it, and this method returns a nonzero result code. Pass handle_errors: false to have the error raised out of this method instead, so your test can assert on it.

Examples:

# Given the following tool:

tool "hello" do
  flag :shout
  def run
    puts message
  end
  def message
    shout ? "HELLO" : "hello"
  end
end

# You can test the tool's output as follows:

class MyTest < Minitest::Test
  include Toys::Testing
  def test_output_without_shout
    assert_output("hello\n") do
      result = toys_run_tool(["hello"])
      assert_equal(0, result)
    end
  end
  def test_with_shout
    assert_output("HELLO\n") do
      result = toys_run_tool(["hello", "--shout"])
      assert_equal(0, result)
    end
  end
end

Parameters:

  • cmd (String, Array<String>)

    The command to execute.

  • cli (Toys::CLI) (defaults to: nil)

    The CLI to run the tool under. Optional.

  • verbosity (Integer) (defaults to: 0)

    Initial verbosity. Default is 0.

  • wrap_errors (boolean) (defaults to: true)

    Whether to wrap an error raised by the tool in a ContextualError that identifies the tool. Default is true. Pass false to work with the original exception.

  • handle_errors (boolean) (defaults to: true)

    Whether to pass an error raised by the tool to the error handler, which reports it and returns a result code. Default is true. Pass false to raise it out of this method instead. See Runner#run for the full contract.

Returns:

  • (Integer)

    The integer result code (i.e. 0 for success).



144
145
146
147
148
149
150
151
# File 'lib/toys/testing.rb', line 144

def toys_run_tool(cmd, cli: nil, verbosity: 0, wrap_errors: true, handle_errors: true)
  cli ||= toys_cli
  cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
  cli.runner.run(cmd,
                 verbosity: verbosity,
                 wrap_errors: wrap_errors,
                 handle_errors: handle_errors)
end