ruby rr: How to stub/mock built-in variables like $? - ruby

While writing a unit test for following:
def foo()
popen_response = ""
IO.popen(#packaging_cmd, :err=>[:child, :out]) {|io| popen_response = io.read }
rc = $?
#log.debug{"Response from IO.popen() : #{popen_response}. rc: '#{rc}'"}
if rc.exitstatus != 0
#log.error{"Packaging failed. rc: '#{rc}'"}
raise PackagingError.new("Packaging failed. rc: '#{rc}'")
end
end
I'm stuck because I don't know how to mock/stub what $? evaluates to. I can hack around by creating a function that returns $? and mock that function or tinker with teh command passed to IO.popen(), but I wonder if there is any official way in RR that I can use.
I'm using rr with stock Test::Unit::TestCase
require 'test/unit'
require 'rr'

I'm not sure which mocking framework you are using, but in RSpec:
allow_any_instance_of(Process::Status).to receive(:exitstatus).and_return(0)

Related

Return undefined in RSpec mock

I have written some code that makes use of Ruby's defined? keyword.
I would like to test this using RSpec but I cannot think of a way to make the method return something thats undefined for testing purposes.
Here is my test -
my_service = class_double('MyService').as_stubbed_const(transfer_nested_constants: true)
allow(my_service).to receive(:something).and_return(UNDEFINED_HERE)
expect(something_else).to receive(:fail!)
Here is my code -
has_result = false
attempts = 0
until has_result do
result = MyService.something(object)
if defined? result
has_result = true
else
attempts += 1
end
end
Thanks in advance!!

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.

Testing comand-line output using rspec

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)

Ruby Test:Unit, how to know fail/pass status for each test case in a test suite?

This question sounds stupid, but I never found an answer online to do this.
Assume you have a test suite like this page:
http://en.wikibooks.org/wiki/Ruby_Programming/Unit_testing
or code:
require "simpleNumber"
require "test/unit"
class TestSimpleNumber < Test::Unit::TestCase
def test_simple
assert_equal(4, SimpleNumber.new(2).add(2) )
assert_equal(4, SimpleNumber.new(2).multiply(2) )
end
def test_typecheck
assert_raise( RuntimeError ) { SimpleNumber.new('a') }
end
def test_failure
assert_equal(3, SimpleNumber.new(2).add(2), "Adding doesn't work" )
end
end
Running the code:
>> ruby tc_simpleNumber2.rb
Loaded suite tc_simpleNumber2
Started
F..
Finished in 0.038617 seconds.
1) Failure:
test_failure(TestSimpleNumber) [tc_simpleNumber2.rb:16]:
Adding doesn't work.
<3> expected but was
<4>.
3 tests, 4 assertions, 1 failures, 0 errors
Now, how to use a variable (what kind?) to save the testing results?
e.g., an array like this:
[{:name => 'test_simple', :status => :pass},
{:name => 'test_typecheck', :status => :pass},
{:name => 'test_failure', :status => :fail},]
I am new to testing, but desperate to know the answer...
you need to execute your test script file, that's it, the result will display pass or fails.
Suppose you save file test_unit_to_rspec.rb, after that execute below command
ruby test_unit_to_rspec.rb
Solved the problem with setting a high verbose level, in a test runner call.
http://ruby-doc.org/stdlib-1.8.7/libdoc/test/unit/rdoc/Test/Unit/UI/Console/TestRunner.html
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class MySuperSuite < Test::Unit::TestSuite
def self.suite
suites = self.new("My Super Test Suite")
suites << TestSimpleNumber1
suites << TestSimpleNumber2
return suites
end
end
#run the suite
# Pass an io object
#new(suite, output_level=NORMAL, io=STDOUT)
runner = Test::Unit::UI::Console::TestRunner.new(MySuperSuite, 3, io)
results will be saved in the io stream in a nice format fo each test case.
What about using '-v' (verbose):
ruby test_unit_to_rspec.rb -v
This should show you a lot more information
You can check out another of Nat's posts for a way to capture the results. The short answer to your question is that there is no variable for capturing the results. All you get is:
Loaded suite My Special Tests
Started
..
Finished in 1.000935 seconds.
2 tests, 2 assertions, 0 failures, 0 errors
Which is not very helpful if you want to report to someone else what happened. Nat's other post shows how to wrap the Test::Unit in rspec to get a better result and more flexibility.
class Test::Unit::TestCase
def setup
#id = self.class.to_s()
end
def teardown
#test_result = "pass"
if(#_result.failure_count > 0 || #_result.error_count > 0)
#test_result = "fail"
# making sure no errors/failures exist before the next test case runs.
i = 0
while(i < #_result.failures.length) do
#_result.failures.delete_at(i)
i = i + 1
end
while(i < #_result.errors.length) do
#_result.errors.delete_at(i)
i = i + 1
end
#test_result = "fail"
end # if block ended
puts"#{#id}: #{#test_result}"
end # teardown definition ended
end # class Test::Unit::TestCase ended
Example Output :
test1: Pass
test2: fail
so on....

Proper Assert_Raise Unit Testing and Use of Exception Class

I am working on Exercise 49 of Learn Ruby the Hard Way
The exercise asks to write a unit test for each function provided. One of the items I am testing is if a proper exception is raised. It is suggested that we use assert_raise for this purpose.
Here is the code I am testing:
class ParserError < Exception
end
Pair = Struct.new(:token, :word)
def peek(word_list)
begin
word_list.first.token
rescue
nil
end
end
def match(word_list, expecting)
word = word_list.shift
if word.token == expecting
word
else
nil
end
end
def skip_word(word_list, token)
while peek(word_list) == token
match(word_list, token)
end
end
def parse_verb(word_list)
skip_word(word_list, :stop)
if peek(word_list) == :verb
return match(word_list, :verb)
else
raise ParserError.new("Expected a verb next.")
end
end
And here is the test, for the function parse_verb:
def test_parse_verb
list_one = [Pair.new(:verb, 'go'), Pair.new(:noun, 'king')]
assert_equal(parse_verb(list_one), Pair.new(:verb, 'go'))
list_two = [Pair.new(:noun, 'player') ,Pair.new(:verb, 'go'), Pair.new(:noun, 'king')]
assert_raise(ParserError.new("Expected a verb next.")) {parse_verb(list_two)}
end
When I run the test, it fails and here is the message I get:
Larson-2:test larson$ ruby test_sentence.rb
Loaded suite test_sentence
Started
.F..
Finished in 0.001204 seconds.
1) Failure:
test_parse_verb(SentenceTests) [test_sentence.rb:36]:
[#<ParserError: Expected a noun or direction next.>] exception expected, not
Class: <ParserError>
Message: <"Expected a verb next.">
---Backtrace---
/Users/larson/Ruby/projects/ex48/lib/sentence.rb:45:in `parse_verb'
test_sentence.rb:36:in `block in test_parse_verb'
---------------
4 tests, 7 assertions, 1 failures, 0 errors, 0 skips
Test run options: --seed 40627
Based on my understanding of the assert_raise function, this test should pass, is there something wrong with the way I am using it?
If anybody would like a full source code of all the files I am working with I it is available here
assert_raise expects one or more exception classes as its parameters, rather than an instance of the required exception.
It also returns the exception raised so if you want to assert the message (or any other properties) you can do that separately. So try replacing:
assert_raise(ParserError.new("Expected a verb next.")) {parse_verb(list_two)}
with:
exception = assert_raise(ParserError) {parse_verb(list_two)}
assert_equal("Expected a noun or direction next.", exception.message)
For some reason, the answer given above didn't work for me (i'm using Ruby 2.0.0).
I has to wrap the Error class name in a String for it to work:
assert_raise("RuntimeError") {
# some code to trigger the error
}

Resources