Ruby minitest test output of method that requires user input

1.2k views Asked by At

RESOLVED


Question 1: I want to write a test for file_choice_reader in the following class.

The class prints a list of files of certain types to command line and lets the user choose one by typing in the index number.

class File_chooser

  #shortened for readability

  def file_choice_suggester
    file_list = file_list_generator
    if file_list.count > 0
      file_list.each_with_index do |file, index|
        puts index.to_s + ' ' + file
      end
    else 
      puts 'Neither .fcv nor .tmpl nor .ipa nor .apf files in directory.'  
    end
    file_list
  end

  def file_choice_reader
    unless File.exists? 'Cookie.txt'
      file_list = file_choice_suggester
      puts 'Choose file by typing index number!'
      chosen_file = STDIN.gets.chomp
      if /[^0-9]/.match(chosen_file) || chosen_file.to_i >= file_list.count
        abort ('No valid index number.')
      else 
        chosen_file = chosen_file.to_i
      end
      cookie_writer(  file_list[chosen_file].to_s )
      system 'cls'
      puts 'You chose file: ' + file_list[chosen_file].to_s
      path_and_file = file_list[chosen_file].to_s
    else
      self.hints_hash = hints_hash.merge( 'cookie_del' => '* Change file by typing command: del Cookie.txt *' )
      pre_chosen_file = File.read('Cookie.txt')
      path_and_file = pre_chosen_file.chomp.to_s
    end
    path_and_file
  end

end

My test looks like this (I get prompted to type in the index number but it still says that the output is ""):

class TestFile_chooser < MiniTest::Unit::TestCase 
  def setup
    @file_chooser = File_chooser.new
  end

  def test_file_choice_reader_produces_confirmation_output
    assert_output( /You chose file/ ) { @file_chooser.file_choice_reader }
  end 
end

The output of file_choice_reader is always "". How can I add the sequence of getting user input and /then/ measuring output?

Question 2: This is a short question. The same test class as above also contains

  def test_file_choice_suggester_produces_output
    assert_output( /apf|fcv|tmpl|ipa/ ) {  @file_chooser.file_choice_suggester }
  end 

This test passes. But it leaves me with "1 runs, 2 assertions". That leaves me puzzled. How can 1 test in 1 run produce 2 (??) assertions?

I would be very glad about help. The minitest discussion on the internet doesn't seem to cover these things. Maybe it is too fundamental?

(I am grateful for all other remarks about the code in comments, too. Thank you for helping.)


UPDATE (Question 1)

With the help of the reply below, my newest version of the test using the example in http://www.ruby-doc.org/core-2.1.5/Module.html

@file_chooser.instance_eval do 
  self.create_method( :puts ) {|arg| printed = arg} 
end

The test ran without error ... BUT ... it STILL tells me: "Failed refutation. No message given."

Thanks for the help so far! Thanks also for all hints how to figure it out.

[Adding here for code is difficult to read in comments.]


UPDATE 2 (Question 1)

I followed the advice on a different question to explicitely require the minitest gem. I put this above my testfile code:

require 'rubygems'
gem 'minitest'
require 'minitest/autorun'
require_relative 'falcon'

(If this is redundant please let me know.)

The following testcode now neither produces an error nor a failure anymore:

  def test_file_choice_suggester_produces_output
    assert_output( /apf|fcv|tmpl|ipa/ ) {  @file_chooser.file_choice_suggester }
  end 

Thanks all for your help!

1

There are 1 answers

6
Josh Bodah On

For #1:

One approach could be to stub out the IO object that your test is using by overriding the puts method. The other would be to refactor your design so that your methods are smaller and easier to test.

The puts override would go something like this:

In your test:

@file_chooser = File_chooser.new
printed = nil
@file_chooser.instance_eval do
  # Open up the instance and stub out the puts method to save to a local variable
  define_method :puts, Proc.new {|arg| printed = arg}
end
# Run code
refute printed.nil?

Alternatively, you could run an expectation on the $STDOUT object to make sure it gets the correct call (see Mocha for Ruby)