How to check stdin for CLI using rspec

I am making a small Ruby program and cannot understand how to write RSpec specifications that mimic multiple user command line inputs (the functionality itself works). I think this StackOverflow answer probably covers the soil that is closest to where I am, but this is not exactly what I need. I use Thor for the command line interface, but I don't think this is a problem with anything in Thor.

The program can read commands either from a file or from the command line, and I was able to successfully write tests for reading in their execution. Here is the code:

cli.rb

class CLI < Thor # ... method_option :filename, aliases: ['-f'], desc: "name of the file containing instructions", banner: 'FILE' desc "execute commands", "takes actions as per commands" def execute thing = Thing.new instruction_set do |instructions| instructions.each do |instruction| command, args = parse_instruction(instruction) # private helper method if valid_command?(command, args) # private helper method response = thing.send(command, *args) puts format(response) if response end end end end # ... no_tasks do def instruction_set if options[:filename] yield File.readlines(options[:filename]).map { |a| a.chomp } else puts usage print "> " while line = gets break if line =~ /EXIT/i yield [line] print "> " end end end # .. end 

I successfully tested the execution of the commands contained in the file with this code:

specifications / cli _spec.rb

 describe CLI do let(:cli) { CLI.new } subject { cli } describe "executing instructions from a file" do let(:default_file) { "instructions.txt" } let(:output) { capture(:stdout) { cli.execute } } context "containing valid test data" do valid_test_data.each do |data| expected_output = data[:output] it "should parse the file contents and output a result" do cli.stub(:options) { { filename: default_file } } # Thor options hash File.stub(:readlines).with(default_file) do StringIO.new(data[:input]).map { |a| a.strip.chomp } end output.should == expected_output end end end end # ... end 

and the above valid_test_data has the following form:

Support /utilities.rb

 def valid_test_data [ { input: "C1 ARGS\r C2\r C3\r C4", output: "OUTPUT\n" } # ... ] end 

What I want to do now is the same thing, but instead of reading each command from the "file" and executing it, I want to somehow imitate the user by entering text in stdin . The code below is completely wrong, but I hope it can convey the direction I want to go.

specifications / cli _spec.rb

 # ... # !!This code is wrong and doesn't work and needs rewriting!! describe "executing instructions from the command line" do let(:output) { capture(:stdout) { cli.execute } } context "with valid commands" do valid_test_data.each do |data| let(:expected_output) { data[:output] } let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } } it "should process the commands and output the results" do commands.each do |command| cli.stub!(:gets) { command } if command == :report STDOUT.should_receive(:puts).with(expected_output) else STDOUT.should_receive(:puts).with("> ") end end output.should include(expected_output) end end end end 

I tried using cli.stub(:puts) in different places and, as a rule, changed this code many times, but it seems that I could not get any of my stubs to put the data in stdin. I do not know if I can analyze the set of inputs that I expect from the command line in the same way as with the command file, or what code structure I should use to solve this problem. If someone who developed command line applications could call back, that would be great. Thanks.

+4
source share
4 answers

In the end, I found a solution that, in my opinion, pretty closely reflects the code to execute instructions from a file. I overcame the main obstacle, finally realizing that I could write cli.stub(:gets).and_return and pass it to the array of commands that I wanted to execute (as parameters thanks to the splat * operator), and then pass the "EXIT" command to it The end. So simple, but so elusive. Many thanks to this question and StackOverflow's answer for pushing me to the line.

Here is the code:

 describe "executing instructions from the command line" do let(:output) { capture(:stdout) { cli.execute } } context "with valid commands" do valid_test_data.each do |data| let(:expected_output) { data[:output] } let(:commands) { StringIO.new(data[:input]).map { |a| a.strip } } it "should process the commands and output the results" do cli.stub(:gets).and_return(*commands, "EXIT") output.should include(expected_output) end end end # ... end 
+2
source

Instead of omitting the universe, I think a few bits of indirection will help you write unit test for this code. The simplest thing you can do is enable the input of an IO object for output and by default STDOUT :

 class CLI < Thor def initialize(stdout=STDOUT) @stdout = stdout end # ... @stdout.puts # instead of raw puts end 

Then in your tests, you can use StringIO to check what was printed:

 let(:stdout) { StringIO.new } let(:cli) { CLI.new(stdout) } 

Another option is to use Aruba or something like that, and write full inclusion integration tests where you actually run your program. This also has other problems (for example, being non-destructive and not twisting / using system or user files), but will be better testing your application.

Aruba is a cucumber, but claims can use RSPec. Or, you can use the Aruba Ruby API (which is undocumented, but public and works fine) to do this without the hassle of Gherkin.

In any case, it will be easier for you to make your code more convenient for testing.

+7
source

Have you watched Aruba ? It allows you to write Cucumber function tests for command line programs. You can define your CLI entry this way.

You can write function descriptions using RSpec so that it is not completely new.

+1
source

You can drown all Thor::Actions with Rspec allow_any_instance_of

here is one example:

 it "should import list" do allow_any_instance_of(Thor::Actions).to receive(:yes?).and_return(true) end 
0
source

Source: https://habr.com/ru/post/1437187/


All Articles