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 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 hello\n", result.captured_out)
  end
  def test_with_shout
    result = toys_exec_tool(["hello", "--shout"])
    assert_equal("HELLO HELLO\n", result.captured_out)
  end
end

Parameters:

  • cmd (String, Array<String>)

    The command to execute.

  • opts (keywords)

    The command options.

Yield Parameters:

Returns:



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/toys/testing.rb', line 157

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.run(*cmd)) }
  self.class.toys_exec.exec_proc(tool_caller, **opts, &block)
end

#toys_load_tool(cmd, cli: nil) {|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 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.

Yield Parameters:

Returns:

  • (Object)

    The value returned from the block.



72
73
74
75
76
# File 'lib/toys/testing.rb', line 72

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

#toys_run_tool(cmd, cli: nil) ⇒ 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.

Parameters:

  • cmd (String, Array<String>)

    The command to execute.

Returns:

  • (Integer)

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



97
98
99
100
101
# File 'lib/toys/testing.rb', line 97

def toys_run_tool(cmd, cli: nil)
  cli ||= toys_cli
  cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
  cli.run(*cmd)
end