1

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:

  1. 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

  1. 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.

  2. Some other configuration that I am not aware of as a newbie to RSpec

  3. Investigate alternative options, e.g. minitest, that maybe do not modify the ARGV array??

Further information regarding options 3 and 4 appreciated....

anon_dcs3spp
  • 2,342
  • 2
  • 28
  • 62
  • Which line is causing the error? – BobRodes Jul 10 '20 at 14:51
  • @bobrodes Thanks for responding, appreciated :) The ```end.parse!``` line in the *get* method raised the error – anon_dcs3spp Jul 10 '20 at 14:57
  • Sorry, I see you mentioned that. That doesn't sound like an issue with your test suite. Have you run your `#get` method outside of the test suite, for example `p args.baseurl`? – BobRodes Jul 10 '20 at 18:08
  • @bobrodes Thanks, yeah have tried running it from the console outside of rspec and verified using *puts* – anon_dcs3spp Jul 10 '20 at 22:51
  • Sorry, that's all I got. :) Don't know RSpec at all; I use MiniTest. Good luck! – BobRodes Jul 10 '20 at 23:37
  • @bobrodes Thanks. Might have to try MiniTest. I think what is happening is maybe that RSpec options are being passed down into STDIN ..or...the STDIN options are being collected by RSpec and not the test script. Reading this [post](https://stackoverflow.com/questions/7077026/unable-to-use-optionparser-and-rspec/7077671#7077671) seems to be suggestive that this functionality is not possible with rspec....if true then will migrate over to MiniTest. – anon_dcs3spp Jul 11 '20 at 09:03
  • @bobrodes Added further information to question and potential solutions....Looks like rspec ARGV options are passed into test script with the test stub options piggybacked onto these.....Thanks for suggestion of MiniTest...will investigate...appreciated :) – anon_dcs3spp Jul 11 '20 at 10:01
  • What I like about MiniTest is that it's pure Ruby. You write all of your tests as Ruby methods. Being lazy by nature, the smaller the learning curve, the more I like it. That said, I'm sure RSpec is more feature-rich than MiniTest, so it might require less overhead to develop sophisticated tests with it. – BobRodes Jul 11 '20 at 19:38
  • @bobrodes In agreement....looking into MiniTest. Thanks for recommendation, appreciated :) – anon_dcs3spp Jul 12 '20 at 09:30
  • If you want some sample stuff to look at, [here's](https://github.com/RobertRodes/todolist_project) a class project I did at Launch School. All the MiniTest stuff is in the `test` folder. – BobRodes Jul 12 '20 at 20:30
  • @bobrodes Many thanks for the link, appreciated :) Will give it a try :) – anon_dcs3spp Jul 13 '20 at 15:43

1 Answers1

0

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.

kgilpin
  • 2,201
  • 18
  • 18
  • Thanks @kgilpin Tried code for ```STDIN.should_receive``` but received error about should_receive not being defined as a method. Tried ```allow(STDIN).to receive(:read).and_return("--base-url=https://example.com --yaml=valid.yml")``` but *url* is nil when the test is run. Running the program from the console outside of RSpec shows that url is being assigned to for the code under test. – anon_dcs3spp Jul 10 '20 at 22:50
  • 1
    updated question with further details....Looks like RSpec is designed so that ARGV state is made available to all test scripts.....OptionParser is receiving ARGV with RSpec's state – anon_dcs3spp Jul 11 '20 at 10:17