go-cli-test-support
A Ruby gem that compiles Go CLI applications to binaries and enables testing with RSpec using a DSL-like syntax for command-line arguments.
RSpec test helper for Go CLI applications that simplifies testing by handling compilation, execution, and environment isolation automatically.
What is this?
This library is a helper for testing Go CLI applications with RSpec.
While you would normally use Go's testing package to test Go applications, this library allows you to:
- Compile your Go app to a binary
- Write command-line arguments using RSpec's DSL-like syntax
- Easily capture stdout/stderr/exit_code
- Completely isolate the environment (each test gets its own
$HOME)
# You can write tests like this
exec('add', '10', '5')
expect(exit_code).to eq(0)
expect(stdout.strip).to eq('15.00')
Features
- 🔨 Automatic compilation - Builds your Go CLI app before tests
- 📦 Binary name detection - Extracts binary name from
go.mod - 📁 Smart directory detection - Automatically finds
cmd/directory if present - 🔒 Environment isolation - Each test runs in isolated temp directory with custom
$HOME - 🎯 Simple API - Easy setup/teardown and execution methods
- ⚡ Timeout control - Configurable timeout for command execution
- 🐛 Debug mode - Detailed logging for troubleshooting
- 🔧 Custom build flags - Support for any Go build options
- ✅ Error handling - Clear error messages for common issues
Installation
Add this line to your application's Gemfile:
gem 'go-cli-test-support'
And then execute:
bundle install
Or install it yourself as:
gem install go-cli-test-support
Usage
Basic Example
require 'rspec'
require 'go-cli-test-support'
RSpec.describe 'My CLI App' do
include GoTestHelper
before(:each) do
setup_go_test('/path/to/your/go/project')
end
after(:each) do
teardown_go_test
end
it 'runs successfully' do
exec('--version')
expect(exit_code).to eq(0)
expect(stdout).to include('1.0.0')
end
end
Directory Structure Support
The helper supports multiple Go project structures:
Standard Structure
my-project/
├── go.mod
└── main.go
cmd/ Directory Structure
my-project/
├── go.mod
└── cmd/
└── main.go
The helper automatically detects and uses the cmd/ directory if present.
API Reference
setup_go_test(project_dir, compile_path: nil, build_flags: [])
Prepares the test environment:
- Creates isolated temporary directory
- Compiles the Go binary
- Detects binary name from
go.mod
Parameters:
project_dir(String): Path to your Go project root (wherego.modis located)compile_path(String, optional): Override the directory to compile frombuild_flags(Array, optional): Custom Go build flags (e.g.,-ldflags,-tags)
Raises:
ArgumentError: If project directory doesn't exist orgo.modis missing
Example:
# Auto-detect compile directory
setup_go_test('/path/to/project')
# Explicit compile path
setup_go_test('/path/to/project', compile_path: '/path/to/project/cmd')
# With custom build flags
setup_go_test('/path/to/project', build_flags: ['-ldflags', '-s -w'])
# Multiple options
setup_go_test(
'/path/to/project',
compile_path: '/path/to/project/cmd',
build_flags: ['-v', '-tags', 'integration']
)
teardown_go_test
Cleans up the temporary test directory. Call this in after(:each) or after(:all).
exec(*args, stdin: nil, timeout: 30)
Executes the compiled binary with given arguments.
Parameters:
*args: Command-line arguments to pass to the binarystdin(String, optional): Data to send to stdintimeout(Integer, optional): Command timeout in seconds (default: 30)
Returns: self (for method chaining)
Timeout Behavior:
- If command exceeds timeout,
exit_codewill be 124 stderrwill contain timeout messagestdoutwill be empty
Example:
exec('add', 'task')
exec('list')
exec('input', stdin: "some data\n")
# Custom timeout
exec('long-command', timeout: 60)
# Check for timeout
exec('command', timeout: 5)
if exit_code == 124
puts "Command timed out!"
end
Accessors
After calling exec, you can access:
stdout- Standard output from the commandstderr- Standard error from the commandexit_code- Exit code of the commandtest_dir- Path to the isolated test directory
Debug Mode
Enable debug mode to see detailed execution information:
self.debug = true # Enable debug output
self.debug = false # Disable debug output
When enabled, you'll see:
- Build command details
- Execution details
- Exit codes
- stdout/stderr content
Complete Example
require 'rspec'
require 'go-cli-test-support'
RSpec.describe 'Todo CLI' do
include GoTestHelper
let(:project_dir) { File.('../my-todo-app', __dir__) }
before(:each) do
setup_go_test(project_dir)
end
after(:each) do
teardown_go_test
end
it 'shows usage when no arguments' do
exec
expect(exit_code).to eq(1)
expect(stdout).to include('Usage:')
end
it 'can add and list todos' do
exec('add', 'Buy milk')
expect(exit_code).to eq(0)
expect(stdout).to include('Added')
exec('list')
expect(stdout).to include('Buy milk')
end
it 'isolates data between tests' do
# Each test gets a fresh $HOME directory
exec('add', 'Task 1')
data_file = File.join(test_dir, 'todos.json')
expect(File.exist?(data_file)).to be true
end
end
Advanced Usage
Debug Mode
RSpec.describe 'My App' do
include GoTestHelper
before(:each) do
self.debug = true # Enable detailed logging
setup_go_test(project_dir)
end
it 'debugs execution' do
exec('command')
# [GoTestHelper DEBUG] Executing: /tmp/app_test_xxx/app command
# [GoTestHelper DEBUG] Exit code: 0
end
end
Custom Build Flags
before(:each) do
# Production build with optimizations
setup_go_test(
project_dir,
build_flags: ['-ldflags', '-s -w', '-tags', 'production']
)
end
Timeout Handling
it 'handles long-running commands' do
exec('backup', timeout: 300) # 5 minutes
if exit_code == 124
puts "Backup timed out"
end
end
Error Handling
it 'handles setup errors gracefully' do
expect {
setup_go_test('/invalid/path')
}.to raise_error(ArgumentError, /does not exist/)
end
How It Works
- Compilation: Compiles your Go app to a temporary directory
- Binary Naming: Extracts the binary name from your
go.modmodule path (e.g.,github.com/user/my-app→my-app) - Isolation: Sets
$HOMEto the test directory, isolating config files and data - Execution: Runs the binary with provided arguments and captures output
Requirements
- Ruby >= 2.7.0
- Go installed and available in PATH
- RSpec ~> 3.12
Development
After checking out the repo, run:
bundle install
rspec
Contributing
Bug reports and pull requests are welcome on GitHub.
License
The gem is available as open source under the terms of the MIT License.