Rspec user input while loop tests - ruby

I am using RSpec to test my rock paper scissors game. Included in my begin_game function I have the following code:
user_input = gets.chomp.downcase.to_sym
while !choices.include? user_input
puts "Please choose a valid selection : rock, paper, or scissors"
user_input = gets.chomp.downcase.to_sym
end
I am trying to test for different possible user_inputs. I have tried this:
let(:new_game) {RockPaperScissors.new}
.......
context 'validate that the user input is one of the given choices' do
it 'should prompt the user for a new input if the original one is invalid' do
new_game.stub(:gets) {"r"}
expect(new_game.begin_game).to eq("Please choose a valid selection : rock, paper, or scissors")
end
end
but this results in an infinite loop of "Please choose a valid selection ..." being outputted to Terminal. I read the RSpec mocking documentation but it was difficult for me to understand.

The reason why it's looping is because new_game.stub(:gets) { "r" } will always return r no matter how many times you call it. Thus user_input will never contain valid input and your test will run forever.
To fix this, you should make new_game#gets return a valid selection after a certain number of tries.
For example,
new_game.stub(:gets) do
#counter ||= 0
response = if #counter > 3 # an arbitrary threshold
"rock"
else
"r"
end
#counter += 1
response
end
This would cause your test to print Please choose a valid selection... 4 times and then terminate.
Depending on how you implemented RockPaperScissors#begin_game, the test you wrote would still not pass. This is because puts("a string") will always return nil. Moreover, a while loop will also return nil. So at no point would the above snippet of code return the string "Please choose a valid selection : rock, paper, or scissors".
An implementation of begin_game that would pass is:
def begin_game
user_input = gets.chomp.downcase.to_sym
if choices.include? user_input
# return something here
else
"Please choose a valid selection : rock, paper, or scissors"
end
end
but at that point, I would probably rename it to handle_move, and have it accept an argument as a parameter to avoid stubbing gets in the first place.
def handle_move(input)
if choices.include? input
"Great move!"
else
"Please choose a valid selection : rock, paper, or scissors"
end
end

Related

How do I return an error message when the user inputs wrong info?

I have a program that displays a numbered list and asks the user to input either a number or name from the list, and loops a block until the user enters "exit", after which it ends.
I want to add a line or two that puts an error message like, "Sorry, I don't seem to understand your request" if the user inputs something that is not on the list (name/number) and is not the word "exit".
I can't seem to figure it out. Any advice? My current code is below.
def start
display_books
input = nil
while input != "exit"
puts ""
puts "What book would you more information on, by name or number?"
puts ""
puts "Enter list to see the books again."
puts "Enter exit to end the program."
puts ""
input = gets.strip
if input == "list"
display_books
elsif input.to_i == 0
if book = Book.find_by_name(input)
book_info(book)
end
elsif input.to_i > 0
if book = Book.find(input.to_i)
book_info(book)
end
end
end
puts "Goodbye!!!"
end
Seems that you should add an elsif statement in this if:
if book = Book.find_by_name(input)
book_info(book)
elsif input != 'exit'
puts "Sorry, I don't seem to understand your request"
end
A good template for an interpreter is to build around Ruby's very capable case statement:
loop do
case (gets.chomp.downcase)
when 'list'
display_books
when /\Afind\s+(\d+)/
if book = Book.find($1.to_i)
book_info(book)
end
when /\Afind\s+(.*)/
if book = Book.find_by_name($1)
book_info(book)
end
when 'exit'
break
else
puts "Not sure what you're saying."
end
end
Although this involves regular expressions, which can be a bit scary, it does give you a lot of flexibility. \A represents "beginning of string" as an anchor, and \s+ means "one or more spaces". This means you can type in find 99 and it will still work.
You can create a whole command-line interface with it if you take the time to specify the commands clearly. Things like show book 17 and delete book 17 are all possible with a bit of tinkering.

Begin rescue retry error

If this code executes the else statement, I want it to retry either from rescue or from begin.
Running the code asks me for an input and, when I input it, the code doesn't work.
1- What can I do to make this code work with the else statement running the retry?
2- Why does removing rescue create a retry Syntax Error?
# Creates input method
def input
x = gets.to_i
end
#Begins the first choice
begin
puts "What will you do?
1- Get a closer look
2- Go in the opposite direction
Write your input an press enter:"
rescue
#Calls input method
choice = input
if choice == 1
puts "You get a closer look and..."
elsif choice == 2
puts "You go in the opposite direction, out of trouble"
else
puts "Incorrect input, enter a number between the one's avaliables:"
end
#Retries if the choice is error
retry if choice != 1||2
end
Using a rescue block is for handling exceptions, I really don't think it's needed here. A loop will do the job.
puts "What will you do?",
"1- Get a closer look",
"2- Go in the opposite direction",
"Write your input and press enter:"
loop do
choice = gets.to_i
if choice == 1
puts "You get a closer look and..."
break
elsif choice == 2
puts "You go in the opposite direction, out of trouble"
break
else
puts "Incorrect input, enter a number between the ones available:"
end
end

I'm trying to design a simple Ruby calculator and I'm getting an error

So I've been messing around with Ruby for the first time after finishing the codecademy course up to "Object Oriented Programming, Part I" and I decided to start making a calculator. For some reason though, I get this error:
calc.rb:13:in `addition': undefined local variable or method `user_input' for main:Object (NameError)
from calc.rb:21:in `<main>'
I'm confused why it doesn't see my "user_input" array. Is it out of the scope of the method? Did I initialize it wrong?
Here's the code so you can see for yourself, it's obviously nothing sophisticated and it's not finished. I'm just trying to test for addition right now.
#!/usr/bin/env ruby
user_input = Array.new
puts "Would you like to [a]dd, [s]ubtract, [m]ultiply, or [d]ivide? "
type_of_math = gets.chomp
def addition
operator = :+
puts "Please enter the numbers you want to add (enter \"=\" to stop adding numbers): "
until gets.chomp == "="
user_input << gets.chomp.to_i
end
sum = user_input.inject(operator)
return sum
end
case type_of_math
when "a"
addition
when "s"
puts "Test for subtraction"
when "m"
puts "Test for multiplication"
when "d"
puts "Test for division"
else
puts "Wrong"
end
Consider this untested variation on your code. It's more idiomatic:
def addition
user_input = []
puts 'Please enter the numbers you want to add (enter "=" to stop adding numbers): '
loop do
input = gets.chomp
break if input == '='
user_input << input
end
user_input.map(&:to_i).inject(:+)
end
Notice that it puts user_input into the method. It also uses the normal [] direct assignment of an empty array to initialize it. Rather than chomp.to_i each value as it's entered it waits to do that until after the loop exits.
Instead of while loops, consider using loop do. They tend to be more easily seen when scanning code.
Also notice there's no return at the end of the method. Ruby automatically returns the last value seen.

rspec testing a single part of a method

I have this method for a tic tac tow game:
def start(token)
if token == "X"
puts "#{#player1}: Please enter a number for your X to go"
elsif token == "O"
puts "#{#player2}: Please enter a number for your O to go"
end
player_input = STDIN.gets.chomp
locate_player_input(token, player_input)
end
I'm only trying to test to see if the correct thing is puts'd to the terminal. I have this test:
describe "#start" do
context "X player's turns" do
it "prints proper messege" do
expect(STDOUT).to receive(:puts).with("Harry: Please enter a number for your X to go")
game.start("X")
end
end
end
But I have a feeling the game.start("X") line is what is not making this work. How can I write the test to just check if the puts statement is correctly outputted?
I think I figured it out. Since my function was first puts-ing something and then calling the next method to be run, I needed another expect statement. My passing test is as such:
describe "#start" do
context "X player's turns" do
it "prints proper messege" do
expect(STDOUT).to receive(:puts).with("Harry: Please enter a number for your X to go")
expect(game).to receive(:get_input)
game.start("X")
end
end
end
I'm not sure if this is correct, but the test did pass.

Command not found Ruby - trying to create 24 game

I am trying to solve the "24" game. The point of the game is to generate 4 random integers from 1-9 and ask the player to use addition, subtraction, multiplication, or division to get the number 24. My code runs fine until a player enters a number, and then I get "Command not found". Can someone please take a look at this:
def evaluate (input,solved_v)
input = eval (input.to_f)
#convert to a float and then evaluates it; it computes
if input == 24
solved_v = true
puts "great job! you did it!"
else
puts "please try again"
end
end
def test_entry (input)
if input.scan(%r{[^\d\s()+*/-]}).empty?
#this scan detects letters and special characters because only numbers work
true
else
false
end
end
puts
puts "try to use +, -, / or * to"
puts "get 24 from the integers provided"
puts
series = (1..4).collect{ rand(1..9)}
#generates 4 random numbers between 1 and 9
for i in series
puts i
end
puts "Please guess"
solved = false
unless solved = true
user_input = gets.chomp
if test_entry(user_input) == true
evaluate(user_input)
else
puts "invalid characters entered"
puts "please try again"
puts
end
end
There are numerous problems with your program.
Don't put spaces between your method names and parentheses.
eval takes a string argument, not a float.
Ruby passes arguments by value, so solved_v isn't going to get
returned. Make it the return value of your evaluate method. I'd also
suggest renaming your methods to express their boolean intent. See below...
Don't check boolean expressions for equality to true or false, just use them.
def correct?(input)
if eval(input) == 24
puts "great job! you did it!"
true
else
puts "please try again"
false
end
end
def good_entry?(input)
input.scan(%r{[^\d\s()+*/-]}).empty?
end
and they get used as follows
while true
user_input = gets.chomp
if good_entry?(user_input)
break if correct?(user_input)
else
...
end
end
Finally, note that you're not actually checking that the input provided by the user uses only the supplied random numbers.

Resources