Skip to content

Instantly share code, notes, and snippets.

@matthijsgroen
Last active December 17, 2015 20:48
Show Gist options
  • Save matthijsgroen/5669777 to your computer and use it in GitHub Desktop.
Save matthijsgroen/5669777 to your computer and use it in GitHub Desktop.
Command line stubs
#!/usr/bin/env ruby
# a clever way to stub the calls to bundle:
# put this file in fixtures and give the proper permissions:
# chmod +x spec/fixtures/bundle
# see spec/publish_spec.rb for usage:
# add path to the bundle stub to the beginning of the PATH env. var.
# Now when bundle is called, this stub file is used.
command = File.basename(__FILE__)
require 'pathname'
Pathname.new(File.dirname(__FILE__)).join("#{command}_call.log").open('a') do |f|
f.puts "args: #{ARGV.join('|')}"
unless STDIN.tty?
f.puts "stdin: #{$stdin.read}"
end
end
require 'yaml'
call_configurations = Pathname.new(File.dirname(__FILE__)).join("#{command}_stub.yml")
if call_configurations.exist?
config = YAML.load call_configurations.read
config.each do |call_config|
if (call_config[:args] - ARGV).empty?
call_config[:stdout] && $stdout.puts(call_config[:stdout])
call_config[:stderr] && $stderr.puts(call_config[:stderr])
call_config[:file_output].each do |file_config|
Pathname.new(file_config[:file]).open('w') do |file|
file.puts file_config[:message]
end
end if call_config[:file_output]
exit call_config[:status_code] || 0
end
end
end
require 'fileutils'
require 'yaml'
# Command line stubs
# ==================
#
# This helper allows to create a shell command that acts as a stub/spy.
#
# Setup
# -----
#
# first, you should declare a folder where the stubs should be placed.
# It is the best to use a non-existent folder. Since it will be created when the
# first stub is made, and is easy to cleanup.
#
# 1. `my_stub_folder = Pathname.new('tmp/stubs')`
# 2. `my_stub = create_stub_for('mail', my_stub_folder)`
# 3. When you execute your shell script that should use the
# stubs, be sure to give the stubfolder the most preference in the PATH
# `PATH=#{my_stub_folder}:$PATH my_shell_script_to_test.sh`
#
# Stubbing results of calls
# -------------------------
#
# You can manipulate the results of a stub.
# These manipulations are scoped against call arguments:
#
# my_stub.with_args('some', 'arguments').action.action.action
#
# You can set multiple actions to a stub-call using chaining.
#
# ### Setting return status code:
#
# my_stub.with_args(...).and_exit_with(code)
#
# ### Output text to STDERR:
#
# my_stub.with_args(...).outputs_to_stderr(text)
#
# ### Writes a file:
#
# my_stub.with_args(...).outputs_to_file(filename, text)
#
# Verifying calls (using RSpec)
# -----------------------------
#
# my_stub.should have_been_called_with('arg1', 'arg2')
#
# Verifying STDIN (using RSpec)
# -----------------------------
#
# Sometimes you want to verify content that gets piped into a command:
#
# echo "content" | mail
# mail < some_file.txt
#
# So assert the content that gets piped-in:
#
# my_stub.should have_received_through_stdin(content)
#
# Cleanup
# -------
#
# If you use a non-existent folder as target for the stubs (see setup section)
# removing stub files (calllog, stub, stub config) is as easy as:
#
# my_stub_folde.rmtree
#
module StubHelpers
def create_stub_for(command, folder)
folder.mkpath
FileUtils.cp('spec/fixtures/stub', folder.join(command).to_s)
Stub.new(folder.join("#{command}_stub.yml"), folder.join("#{command}_call.log"))
end
class Stub
def initialize(config_path, call_log_path)
@config_path = config_path
@call_log_path = call_log_path
@configuration = {}
end
def with_args(*args)
StubbedCall.new(self).args(*args)
end
def configure_status_code_for(args, status_code)
@configuration[args] ||= {}
@configuration[args][:status_code] = status_code
write_configuration
end
def configure_stderr_output_for(args, message)
@configuration[args] ||= {}
@configuration[args][:stderr] = message
write_configuration
end
def configure_stdout_output_for(args, message)
@configuration[args] ||= {}
@configuration[args][:stdout] = message
write_configuration
end
def configure_file_output_for(args, file, message)
@configuration[args] ||= {}
@configuration[args][:file_output] ||= []
@configuration[args][:file_output] << {
file: file,
message: message
}
write_configuration
end
def has_been_called_with?(*args)
return false unless @call_log_path.exist?
# check in some file if arg occurs
@call_log_path.each_line do |line|
if match = /^args:\s(?<args>.*)$/.match(line)
call_args = match[:args].split('|')
return true if (args - call_args).empty?
end
end
false
end
def call_log
return false unless @call_log_path.exist?
@call_log_path.read
end
def has_received_through_stdin?(message)
return false unless @call_log_path.exist?
# check in some file if arg occurs
return true if stdin.find { |e| e.match message }
false
end
def stdin
content = @call_log_path.read
content[/^args:.*$/] = ''
content.split(/^stdin: /)
end
private
def write_configuration
structure = []
@configuration.each do |args, results|
call = {
args: args,
}.merge results
structure << call
end
@config_path.open('w') do |f|
f.puts structure.to_yaml
end
end
class StubbedCall
def initialize(stub)
@stub = stub
end
def args(*args)
@args = args
self
end
def outputs_to_stderr(message)
@stub.configure_stderr_output_for(@args, message)
self
end
def outputs_to_stdout(message)
@stub.configure_stdout_output_for(@args, message)
self
end
def outputs_to_file(file, message)
@stub.configure_file_output_for(@args, file, message)
self
end
def and_exit_with(status_code)
@stub.configure_status_code_for(@args, status_code)
self
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment