Skip to content
Black-box testing utility for command line tools and gems
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Test Ruby 2.6 on CI Jan 3, 2019
bin Add Dockerfile for easy local testing on Linux Jun 22, 2018
lib v0.5.1 Dec 11, 2018
.gitignore Gitignore Ruby version Jun 22, 2018
Dockerfile Add Dockerfile for easy local testing on Linux Jun 22, 2018
Gemfile Initial commit Apr 13, 2018
Gemfile.lock v0.5.1 Dec 11, 2018
LICENSE.txt Initial commit Apr 13, 2018 Add gem version badge May 12, 2019
Rakefile Initial commit Apr 13, 2018
jet_black.gemspec Bump Rake version Oct 4, 2018


Gem Version CircleCI Coverage Status

A black-box testing utility for command line tools and gems. Written in Ruby, with RSpec in mind. Features:

The temporary directory is discarded after each spec. This means you can write & modify files and run commands (like git init) without worrying about tidying up after or impacting your actual project.


group :test do
  gem "jet_black"

RSpec setup

If you're using RSpec, you can load matchers with the following require (optional):

# spec/spec_helper.rb

require "jet_black/rspec"

Any specs you write in the spec/black_box folder will then have an inferred :black_box meta type, and the matchers will be available in those examples.

Manual RSpec setup

Alternatively you can manually include the matchers:

# spec/cli/example_spec.rb

require "jet_black"
require "jet_black/rspec/matchers"

RSpec.describe "my command line tool" do
  include JetBlack::RSpec::Matchers


Running commands

require "jet_black"

session =
result ="echo foo")

result.stdout # => "foo\n"
result.stderr # => ""
result.exit_status # => 0

Providing stdin data:

session ="./hello-world", stdin: "Alice")

File manipulation

session =

session.create_file "file.txt", <<~TXT
  The quick brown fox
  jumps over the lazy dog

session.create_executable "", <<~SH
  echo "Hello world"

session.append_to_file "file.txt", <<~TXT

# Subdirectories are created for you:
session.create_file "deeper/underground/jamiroquai.txt", <<~TXT
  I'm going deeper underground, hey ha
  There's too much panic in this town

Copying fixture files

It's ideal to create pertinent files inline within a spec, to provide context for the reader, but sometimes it's better to copy across a large or non-human-readable file.

  1. Create a fixture directory in your project, such as spec/fixtures/black_box.

  2. Configure the fixture path in spec/support/jet_black.rb:

    require "jet_black"
    JetBlack.configure do |config|
      config.fixture_directory = File.expand_path("../fixtures/black_box", __dir__)
  3. Copy fixtures across into a session's temporary directory:

    session =
    session.copy_fixture("src-config.json", "config.json")
    # Destination subdirectories are created for you:
    session.copy_fixture("src-config.json", "config/config.json")

Environment variable overrides

session =
result ="printf $FOO", env: { FOO: "bar" })

result.stdout # => "bar"

Provide a nil value to unset an environment variable.

Clean Bundler environment

If your project's test suite is invoked with Bundler (e.g. bundle exec rspec) but you want to run commands like bundle install and bundle exec with a different Gemfile in a given spec, you can configure the session or individual commands to run with a clean Bundler environment.

Per command:

session ="bundle install", options: { clean_bundler_env: true })

Per session:

session = { clean_bundler_env: true })"bundle install")"bundle exec rake")

$PATH prefix

Given the root of your project contains a bin directory containing my_awesome_bin.

Configure the path_prefix to the directory containing with your executable(s):

# spec/support/jet_black.rb

require "jet_black"

JetBlack.configure do |config|
  config.path_prefix = File.expand_path("../../bin", __dir__)

Then the $PATH of each session will include the configured directory, and your executable should be invokable:"my_awesome_bin")

RSpec matchers

Given the RSpec setup is configured, you'll have access to the following matchers:

  • have_stdout which accepts a string or regular expression
  • have_stderr which accepts a string or regular expression
  • have_no_stdout which asserts the stdout is empty
  • have_no_stderr which asserts the stderr is empty

And the following predicate matchers:

  • be_a_success / be_success asserts the exit status was zero
  • be_a_failure / be_failure asserts the exit status was not zero

Example assertions

# spec/black_box/cli_spec.rb

RSpec.describe "my command line tool" do
  let(:session) { }

  it "does the work" do
    expect("my_tool --good")).
      to be_a_success.and have_stdout(/It worked/)

  it "explodes with incorrect arguments" do
    expect("my_tool --bad")).
      to be_a_failure.and have_stderr("Oh no!")

However these assertions can be made with built-in matchers too:

RSpec.describe "my command line tool" do
  let(:session) { }

  it "does the work" do
    result ="my_tool --good")

    expect(result.stdout).to match(/It worked/)
    expect(result.exit_status).to eq 0

  it "explodes with incorrect arguments" do
    result ="my_tool --bad")

    expect(result.stderr).to match("Oh no!")
    expect(result.exit_status).to eq 1

More examples

You can’t perform that action at this time.