This note aims at explaining how to test user input in RSpec.
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.
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.
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
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 :).
What is the "displayer" variable?