Skip to content

Instantly share code, notes, and snippets.

@Kotauror
Created March 24, 2018 12:38
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Kotauror/6993000de0c53206a96879515438950d to your computer and use it in GitHub Desktop.
Save Kotauror/6993000de0c53206a96879515438950d to your computer and use it in GitHub Desktop.
Testing user input in RSpec

Testing user command line input in RSpec.

This note aims at explaining how to test user input in RSpec.

Background

I'm building a Tic-Tac-Toe game in Ruby. Before the game starts, I need to ask the user for name, in what mode (player vs player, player vs computer) he/she wants to play, and for the sign (eg. X or O). In order to test it, I've stubbed the gets method. It means that each time there was a gets.chomp in the code, I've stubbed it in the test in order to pass an user input. This allowed me to fluently run the tests, without the need of passing user input manually.

Examples of code

Single user input

Method:

  def set_player_mode
    puts "Enter 1 to pick human vs computer"
    puts "Enter 2 to pick human vs human"
    puts "Enter 3 to pick computer vs computer"
    while true do
      mode = gets.chomp.to_s
      if mode == "1" then
        return mode
      elsif mode == "2" then
        return mode
      elsif mode == "3" then
        return mode
      else
        puts "Please enter 1, 2 or 3"
      end
    end
  end

Test:

 describe '#set_player_mode' do
     it 'returns the chosen mode in case of valid input' do
       displayer.stub(:gets).and_return("1\n")
       expect(displayer.set_player_mode).to eq("1")
     end
     it 'doesnt return the mode on invalid input' do
       displayer.stub(:gets).and_return("5\n", "1\n")
       expect{displayer.set_player_mode}.to output(
         "Enter 1 to pick human vs computer\nEnter 2 to pick human vs human\n" +
         "Enter 3 to pick computer vs computer\nPlease enter 1, 2 or 3\n"
       ).to_stdout
     end
   end

When Testing for valid input, I've stubbed gets in the following way:

displayer.stub(:gets).and_return("1\n") # `\n` Is for a new line - enter. 

Then I've expected that my method will return me the valid input.

Testing for invalid input was a bit more complicated. I couldn't just pass an invalid input since it would loop my program for eternity in asking for a valid input. I needed to pass first an invalid input and then a valid one:

   displayer.stub(:gets).and_return("5\n", "1\n")

As A result I expected my program to print a message to_stdout asking for a valid input.

Multiple user input

The same rules apply to multiple user input. See the examples below:

Methods:

  def multi_mode_names_signs
    puts "You've picked the human vs human mode"
    puts "First player"
    user_one_data = ask_for_name_and_sign
    puts "Second player"
    user_two_data = ask_for_name_and_sign
    return user_one_data + user_two_data
  end
  
  def ask_for_name_and_sign
    puts "Enter name"
    name = gets.chomp
    puts "#{name}, enter one letter sign to identify you on the board eg. X or O"
    sign = single_character_guard
    return [name, sign]
  end

  def single_character_guard
    while true do
      sign = gets.chomp
      if sign.length == 1 then
        return sign
      else
        puts "Please write only one character"
      end
    end
  end

Test:

    describe '#multi_mode_names_signs' do
      it 'asks for users names and returns them in an array on valid input' do
        displayer.stub(:gets).and_return("Justyna\n", "J\n", "Kota\n", "K\n")
        expect(displayer.multi_mode_names_signs).to eq(["Justyna", "J", "Kota", "K"])
      end
      it 'asks for users names and doesnt return them on invalid input' do
        displayer.stub(:gets).and_return("Justyna\n", "JZ\n", "J\n", "Kota\n", "K\n")
        expect{displayer.multi_mode_names_signs}.to output(
          "You've picked the human vs human mode\nFirst player\nEnter name\n" +
          "Justyna, enter one letter sign to identify you on the board eg. X or O\n" +
          "Please write only one character\n" +
          "Second player\nEnter name\n" +
          "Kota, enter one letter sign to identify you on the board eg. X or O\n"
        ).to_stdout
      end
    end

Preventing tests from writing.

As my tests were asking users for input, it was making my test outputs pretty ugly and hard to read:

Displayer
 At the beginning of the game
   #welcome
     welcomes the user with the name of the game and the rules
 Setting mode, getting names
   #set_player_mode
Enter 1 to pick human vs computer
Enter 2 to pick human vs human
Enter 3 to pick computer vs computer
     returns the chosen mode in case of valid input
     doesnt return the mode on invalid input
   #single_mode_name_sign
You've picked the human vs computer mode
Enter name
Justyna, enter one letter sign to identify you on the board eg. X or O
     asks for user name and sign and returns them on valid input
     asks for user name and sign and doesnt return them on invalid input
   #multi_mode_names_signs
You've picked the human vs human mode
First player
Enter name
Justyna, enter one letter sign to identify you on the board eg. X or O
Second player
Enter name
Kota, enter one letter sign to identify you on the board eg. X or O

In order to get rid of these lines, I added the following before block:

  before do
    allow($stdout).to receive(:write)
  end

It mocks writing functionality (when the program writes something to stdout, in practice - when we use puts.) and replaces it with nothing :).

@ZASMan
Copy link

ZASMan commented Feb 11, 2021

What is the "displayer" variable?

@jramiresbrito
Copy link

What is the "displayer" variable?

up ?

@ZASMan
Copy link

ZASMan commented May 1, 2021

Here's an example of how I ended up testing puts output in the project I was working on after my above post:

require 'stringio'

describe MyClass do
    it 'should test the output do
      $stdout = StringIO.new
      MyClass.run
      $stdout.rewind
      result_string = 'This is the message I expect'
      expect($stdout.string).to include(result_string)
    end
  end
end

@Kotauror
Copy link
Author

What is the "displayer" variable?

I'm so sorry folks, I didn't know anyone is reading this :D
You can see the whole project here: https://github.com/Kotauror/Tic_Tac_Toe_8th_Light

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment