3

I'm trying to write a simple test for a ruby script that uses gets and I'm experiencing some weird behavior.

When I run the test with rspec it passes fine, but as soon as I run rspec with any flags, like rspec -f json or rspec -O rand the test will fail and give me No such file or directory @ rb_sysopen - -O, where -O is whatever the flag that was run.

Stranger still is that if I run rspec and specify the test file like rspec spec/gets_spec.rb, I get a completely different error:

     Failure/Error: expect { require_relative '../gets' }.to output("Hello, Lena!\n").to_stdout                         
       expected block to output "Hello, Lena!\n" to stdout, but output "Hello, Describe \"gets.rb\" do!\n"
                     Diff:                                                                                                              
@@ -1,2 +1,2 @@                                                                                                         
-Hello, Lena!                                                                                                           
+Hello, Describe "gets.rb" do!                                                                                                                                                                                                                
# ./spec/gets_spec.rb:14:in `block (2 levels) in <top (required)>' 

I'm having trouble figuring out the right way to write my test so that this doesn't happen but I'm not sure what part of my code needs to change.

I'm using ruby 2.6.3

I learned about testing input from this question

The script, gets.rb, looks like

puts "Hello, #{gets.chomp.capitalize}!"

My test looks like

describe "gets.rb" do
  before do
      $stdin = StringIO.new("lena")
  end

  after do 
      $stdin = STDIN
  end

  it "should output 'Hello, name!'" , points: 1 do
    allow($stdin).to receive(:gets).and_return("lena")

    expect { require_relative "../gets" }.to output("Hello, Lena!\n").to_stdout
  end
end

Here's the full failure message:

Failures:

  1) gets.rb should output 'Hello, name!'
     Failure/Error: expect { require_relative '../gets' }.to output("Hello, Lena!\n").to_stdout                        
     Errno::ENOENT: 
No such file or directory @ rb_sysopen - -O                                                                          
#./gets.rb:1:in `gets'                     
# ./gets.rb:1:in `gets' 
# ./gets.rb:1:in `<top (required)>' 
# ./spec/gets_spec.rb:14:in `require_relative' 
# ./spec/gets_spec.rb:14:in `block (3 levels) in <top (required)>'
# ./spec/gets_spec.rb:14:in `block (2 levels) in <top (required)>'  
J Woods
  • 138
  • 1
  • 8
  • Thanks for asking this question. I learned about testing scripts by name using require and about output matchers. +1 – Christian Oct 05 '19 at 00:50
  • When I run rspec spec/gets_spec.rb, I get: expected: ("Hello, Lena!") got: ("Hello, Describe \"gets.rb\" do!") I don't know why ... – Christian Oct 05 '19 at 01:18
  • Exactly @Christian. I can't imagine how `$stdin` is getting the content of the spec file. That doesn't make sense to me. – J Woods Oct 05 '19 at 20:05

1 Answers1

1

You can try to use a little different approach:

describe "gets.rb" do
  it "should output 'Hello, name!'" do
    allow_any_instance_of(Object).to receive(:gets).and_return("lena")

    expect { require_relative "../gets.rb" }.to output("Hello, Lena!\n").to_stdout
  end
end

allow_any_instance_of I took from https://makandracards.com/makandra/41096-stubbing-terminal-user-input-in-rspec

Reading user input in console applications is usually done using Kernel#gets.

The page suggests to use Object.any_instance.stub(gets: 'user input') but it's deprecated syntax already and I've updated it.

achempion
  • 794
  • 6
  • 17
  • 2
    Wow, thanks! This definitely solves my issue. I didn't know about `allow_any_instance_of` until now. Definitely useful. +1 – J Woods Oct 05 '19 at 20:03
  • To be complete, I believe the stub should be ```ruby allow_any_instance_of(Object).to receive(:gets).and_return("lena\n") ``` since `gets` always receives the newline. Tests _could_ fail if the implementations of `gets.rb` used `chop` and the input is just `"lena"`. – J Woods Jul 06 '20 at 19:06