Testing comand-line output using rspec - ruby

I'm trying to test a little gem that makes downloads from youtube using 'youtube-dl'.
I want to test the output from the command youtube-dl [url] --get-title but I dont know how I do that.
This is my code:
module Youruby
class Youtube
YT_DL = File.join(File.expand_path(File.dirname(__FILE__)), "../bin/youtube-dl")
def initialize(id)
#id = id
end
def get_title
system(YT_DL, '--get-title', get_url)
end
end
end
And this is my test:
require "spec_helper"
require "youruby"
describe Youruby do
it "get video title" do
video = Youruby::Youtube.new('uaEJvYWc2ag')
video.get_title.should == "FFmpeg-slowmotion.1"
end
end
When I run the tests I get this error:
Failure/Error: video.get_title.should == "FFmpeg-slowmotion.1"
expected: "FFmpeg-slowmotion.1"
got: true (using ==)
Diff:
## -1,2 +1,2 ##
-"FFmpeg-slowmotion.1"
+true
How do I do that?

Seems like your test is OK, and the implementation is failing (so, is OK for the test to report the fail)
On the implementation, Instead of using system method (which return true/false according the return code of the command), use backtick (which return the string with the output of the command)
def get_title
`#{YT_DL} --get-file #{get_url}`
end
ALso, as additional note, is not good for your implementation to depend on external commands (from Unit testing point of view), maybe you want to mock external system command execution (or not, you maybe know what strategy is better for your particular case)

Related

How to test OptionParser with Rspec - RSpec options are stored in ARGV array during testing

I am learning ruby and trying to write a unit test with rspec for the following method:
def get()
options = {}
OptionParser.new do |opt|
opt.banner = 'Usage: validate-gitlab-ci [options]'
opt.on('-f', '--yaml YAML-PATH', 'Path to .gitlab-ci.yml') { |o| options[:yamlFile] = o }
opt.on('-l', '--base-url GitLab url', 'GitLab API url') { |o| options[:baseUrl] = o + API_PATH }
opt.on('-t', '--timeout[TIMEOUT]', Integer, 'Api timeout in seconds') { |o| options[:timeout] = o || 10 }
opt.on('-v', '--version', 'Program version') { |o| options[:version] = o }
end.parse!
validateUrl!(options[:baseUrl])
validateYamlFile!(options[:yamlFile])
#baseUrl = options[:baseUrl]
#pathToYamlFile = options[:yamlFile]
end
The code for my unit test so far is:
RSpec.describe Gitlab::Lint::Client::Args do
describe "#get" do
context "when arguments are valid" do
it "sets baseUrl and pathToYamlFile" do
io = StringIO.new
io.puts "glab-lint --base-url=https://example.com --yaml=valid.ym\n"
io.rewind
$stdin = io
args = Gitlab::Lint::Client::Args.new
args.get()
expect(args.baseUrl).to.eq("https://example.com")
end
end
end
end
I am trying to mock STDIN for OptionParser. However, upon executing the test the following error is displayed:
OptionParser::InvalidOption:
invalid option: --pattern
This is raised by the end.parse! line in the get() method
Has anyone managed to test OptionsParser with stdin mocked?
Update
I think what is happening is that some RSpec options, e.g. --pattern?? are being captured in STDIN and passed to script??? Or .... RSpec is consuming the stdin options??
Reading this post seems to suggest that the desired functionality is not possible with RSpec....if this is indeed true then I will migrate over to using alternative test frameworks in future for CLI projects that use ARGV. There is a workaround suggested here but that suggests using environment variables for capturing commmand line arguments. In this case that would require further refactoring of the software under test, purely to suit the capabilities of the RSpec test framework!!
If I add a puts statement to display the contents of ARGV in the test script it confirms this is the case, with this output:
--pattern
spec/**{,/*/**}/*_spec.rb
[--base-url=https://gitlab.com --yaml=valid.ym]
So.....as a complete newbie to RSpec.....my options are:
Update the signature of the get method to accept an args array:
def get(args)
options = {}
OptionParser.new do |opt|
...
end.parse!(args)
end
This delays the issue with testing the code that reads from ARGV further up the call hierarchy
Modify ARGV shifting the first two arguments out of the array and then after the test has completed restore ARGV to original state. Looks like something similar has already been tried here without success.
Some other configuration that I am not aware of as a newbie to RSpec
Investigate alternative options, e.g. minitest, that maybe do not modify the ARGV array??
Further information regarding options 3 and 4 appreciated....
You can use RSpec to mock STDIN. For example:
STDIN.should_receive(:read).and_return("glab-lint --base-url=https://example.com --yaml=valid.yml")
Alternatively, you can invoke your actual command line program using backticks or system, and assert on the response.

how do you mock dependent methods using rspec

I'm trying to write a custom parser for my cucumber results. In doing so, I want to write rspec tests around it. What I currently have is as follows:
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
The problem is, determine_test_results has a sub method called determine_step_results, which means this is really an integration test between the 2 methods and not a unit test for determine_test_results.
How would I mock out the "response" from determine_step_results?
Assume determine_step_results returns {"status"=>"passed", "scenario_line"=>4}
what I have tried:
pcr.stub(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
and
allow(pcr).to receive(:determine_step_results).and_return({"status"=>"passed", "scenario_line"=>6})
You could utilize stubs for what you're trying to accomplish. Project: RSpec Mocks 2.3 would be good reading regarding this particular case. I have added some code below as a suggestion.
describe 'determine_test_results' do
it 'returns a scenario name as the key of the scenario results, with the scenario_line attached' do
pcr = ParseCucumberJsonReport.new
expected_results = {"I can login successfully"=>{"status"=>"passed", "scenario_line"=>4}}
# calls on pcr will return expected results every time determine_step_results is called in any method on your pcr object.
pcr.stub!(:determine_step_results).and_return(expected_results)
cucumber_results = JSON.parse(IO.read('example_json_reports/json_passing.json'))
pcr.determine_test_results(cucumber_results[0]).should == expected_results
end
end
If all what determine_test_results does is call determine_step_results, you should not really test it, since it is trivial...
If you do decide to test it, all you need to test is that it calls the delegate function, and returns whatever is passed to it:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
subject.determine_test_results(input).should == result
end
end
end
If it is doing anything more (like adding the result to a larger hash) you can describe it too:
describe ParseCucumberJsonReport do
describe '#determine_test_results' do
it 'calls determine_step_results' do
result = double(:result)
input = double(:input)
expect(subject).to receive(:determine_step_results).with(input).and_return(result)
expect(subject.larger_hash).to receive(:merge).with(result)
subject.determine_test_results(input).should == result
end
end
end

Mock file input as file path on Rspec

I have a question on how to use rspec to mock a file input. I have a following code for the class, but not exactly know a why to mock a file input. filepath is /path/to/the/file
I did my search on Google and usually turns out to be loading the actual file instead of mocking, but I'm actually looking the opposite where only mock, but not using the actual file.
module Service
class Signing
def initialize(filepath)
#config = YAML.load_file(filepath)
raise "Missing config file." if #config.nil?
end
def sign() …
end
private
def which() …
end
end
end
Is it possible to use EOF delimiter for this file input mocking?
file = <<EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF
You could stub out YAML.load_file and return parsed YAML from your text, like this:
yaml_text = <<-EOF
A_NAME: ABC
A_ALIAS: my_alias
EOF
yaml = YAML.load(yaml_text)
filepath = "bogus_filename.yml"
YAML.stub(:load_file).with(filepath).and_return(yaml)
This doesn't quite stub out the file load itself, but to do that you'd have to make assumptions about what YAML.load_file does under the covers, and that's not a good idea. Since it's safe to assume that the YAML implementation is already tested, you can use the code above to replace the entire call with your parsed-from-text fixture.
If you want to test that the correct filename is passed to load_file, replace the stub with an expectation:
YAML.should_receive(:load_file).with(filepath).and_return(yaml)
If the idea is to put an expectation on something, I don't see much benefit on this approach of calling YAML.load to fake the return. YAML.load_file actually returns a hash, so instead of doing all that my suggestion would be to simply return a hash:
parsed_yaml = {
"somekey" => {
"someotherkey" => "abc"
}
}
YAML.should_receive(:load_file).with(filepath).and_return(parsed_yaml)
As this is supposed to be a unit test and not an integration test, I think this would make more sense.

Ruby Load multiple scripts from directory with foreach loop

I'm using a loop to load and execute Ruby scripts in a directory. At the moment the script will load the script, but how do I execute it when the only reference to it is the filename in the form of a string?
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if item # the scripts return true/false
numberPassed+=1
else
numberFailed+=1
failed.push(item)
end
numberTested+=1
end
For some reason I'm getting 2 Passed, but it never actually runs the scripts "item" represents.
EDIT: here is an example of a script that would need to be loaded. They all follow this format:
require "watir-webdriver"
class TestScript
puts 'Testing etc etc"...'
browser = Watir::Browser.new :ie
browser.goto "webpage.htm"
browser.text_field(:name => "j_username").set "username"
browser.text_field(:name => "j_password").set "password"
browser.link(:id, "watSubmitLogin").click
browser.wait
browser.link(:id=> 'watCommDir').fire_event("onmouseover")
browser.link(:id=> 'watAddFi').click
browser.wait
...
browser.link(:href, "javascript: submitForm();").click
browser.wait
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
return true
else
puts 'Failed'
browser.close
return false
end
end
I need to somehow tell the main script whether the sub-scripts pass or fail so I can keep track of how many pass/fail/error/total and create a report of all the tests that failed.
Looks like you are doing acceptance testing with Watir and try to do custom test results reporting.
I would recommend to use existing test runners to run all your tests and build custom output formatter for your needs. Existing test runners already solve a lot of issues you will encounter during creation of your own test runner (like how to run tests from specified folder, how to identify failing/successful test etc).
One of the commmon test runners for acceptance tests in Ruby community is Cucumber. Another good alternative is RSpec. Both these libraries support custom formatters:
In RSpec you would need to subclass RSpec::Core::Formatters::BaseFormatter.
In Cucumber you would need to implement class with methods specified in this documentation.
If you want to stay with the current simple implementation, here is one possible approach that is inpired by ruby Regexps: Inside the test set global variable, e.g. $test_succeeded (like $~, $& etc. global variables generated by ruby regular expressions) and then examine this value in your test runner.
In tests
if browser.text.include?( 'The user already Exists')
puts 'Passed'
browser.close
$test_succeeded = true
# ...
In tests runner
Dir.foreach('tests') do |item|
next if item == '.' or item == '..' #removes extra "." or ".."
load dirname + '/' + item #successfully loads the script
if $test_succeeded
# ...
If you have problems running the script then I can recommend to define special method to run tests (similar to RSpec approach):
def test
test_res = yield # call test
$test_results ||= {}
$test_results << test_res # and store its result in arra of test results
end
Then your tests will look like:
require 'file_with_test_method'
require 'watir-webdriver'
test do
# your test code
browser.text.include?( 'The user already Exists') # last expression in the block will be the test result
end

Execute Rspec from Ruby

I am trying to execute rspec from ruby, and get the status or number of failures from a method or something like that. Actually I am running something like this:
system("rspec 'myfilepath'")
but I only can get the string returned by the function. Is there any way to do this directly using objects?
I think the best way would be using RSpec's configuration and Formatter. This would not involve parsing the IO stream, also gives much richer result customisation programmatically.
RSpec 2:
require 'rspec'
config = RSpec.configuration
# optionally set the console output to colourful
# equivalent to set --color in .rspec file
config.color = true
# using the output to create a formatter
# documentation formatter is one of the default rspec formatter options
json_formatter = RSpec::Core::Formatters::JsonFormatter.new(config.output)
# set up the reporter with this formatter
reporter = RSpec::Core::Reporter.new(json_formatter)
config.instance_variable_set(:#reporter, reporter)
# run the test with rspec runner
# 'my_spec.rb' is the location of the spec file
RSpec::Core::Runner.run(['my_spec.rb'])
Now you can use the json_formatter object to get result and summary of a spec test.
# gets an array of examples executed in this test run
json_formatter.output_hash
An example of output_hash value can be found here:
RSpec 3
require 'rspec'
require 'rspec/core/formatters/json_formatter'
config = RSpec.configuration
formatter = RSpec::Core::Formatters::JsonFormatter.new(config.output_stream)
# create reporter with json formatter
reporter = RSpec::Core::Reporter.new(config)
config.instance_variable_set(:#reporter, reporter)
# internal hack
# api may not be stable, make sure lock down Rspec version
loader = config.send(:formatter_loader)
notifications = loader.send(:notifications_for, RSpec::Core::Formatters::JsonFormatter)
reporter.register_listener(formatter, *notifications)
RSpec::Core::Runner.run(['spec.rb'])
# here's your json hash
p formatter.output_hash
Other Resources
Detailed work through
Gist example
I suggest you to take a look into rspec source code to find out the answer. I think you can start with example_group_runner
Edit: Ok here is the way:
RSpec::Core::Runner::run(options, err, out)
Options - array of directories, err & out - streams. For example
RSpec::Core::Runner.run(['spec', 'another_specs'], $stderr, $stdout)
Your problem is that you're using the Kernel#system method to execute your command, which only returns true or false based on whether or not it can find the command and run it successfully. Instead you want to capture the output of the rspec command. Essentially you want to capture everything that rspec outputs to STDOUT. You can then iterate through the output to find and parse the line which will tell you how many examples were run and how many failures there were.
Something along the following lines:
require 'open3'
stdin, stdout, stderr = Open3.popen3('rspec spec/models/my_crazy_spec.rb')
total_examples = 0
total_failures = 0
stdout.readlines.each do |line|
if line =~ /(\d*) examples, (\d*) failures/
total_examples = $1
total_failures = $2
end
end
puts total_examples
puts total_failures
This should output the number of total examples and number of failures - adapt as needed.
This one prints to console and at the same time captures the message. The formatter.stop is just a stub function, I don't know what it is for normally, I had to include it to use DocumentationFormatter. Also the formatter output contains console coloring codes.
formatter = RSpec::Core::Formatters::DocumentationFormatter.new(StringIO.new)
def formatter.stop(arg1)
end
RSpec.configuration.reporter.register_listener(formatter, :message, :dump_summary, :dump_profile, :stop, :seed, :close, :start, :example_group_started)
RSpec::Core::Runner.run(['test.rb','-fdocumentation'])
puts formatter.output.string

Resources