rspec testing a single part of a method - ruby

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.

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.

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 user input while loop tests

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

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

how to test STDIN with RSpec

Ok, need help working out a test. I want to test that this class receives a letter "O" and
that when called the "move_computer" method returns WHATEVER the person enters on the cli. my mental subprocessor tells me this is a simple assign a variable to something to hold the random human input at STDIN. Just not getting it right now...anyone point me in the right direction?
here is my class...
class Player
def move_computer(leter)
puts "computer move"
#move = gets.chomp
return #move
end
end
my test look like...
describe "tic tac toe game" do
context "the player class" do
it "must have a computer player O" do
player = Player.new()
player.stub!(:gets) {"\n"} #FIXME - what should this be?
STDOUT.should_receive(:puts).with("computer move")
STDOUT.should_receive(:puts).with("\n") #FIXME - what should this be?
player.move_computer("O")
end
end
end
Because move_computer returns the input, I think you meant to say:
player.move_computer("O").should == "\n"
I would write the full spec like this:
describe Player do
describe "#move_computer" do
it "returns a line from stdin" do
subject.stub!(:gets) {"penguin banana limousine"}
STDOUT.should_receive(:puts).with("computer move")
subject.move_computer("O").should == "penguin banana limousine"
end
end
end
Here is the answer I came up with...
require_relative '../spec_helper'
# the universe is vast and infinite...it contains a game.... but no players
describe "tic tac toe game" do
context "the player class" do
it "must have a human player X"do
player = Player.new()
STDOUT.should_receive(:puts).with("human move")
player.stub(:gets).and_return("")
player.move_human("X")
end
it "must have a computer player O" do
player = Player.new()
STDOUT.should_receive(:puts).with("computer move")
player.stub(:gets).and_return("")
player.move_computer("O")
end
end
end
[NOTE TO THE ADMINS...it would be cool if I could just select all my code text and right indent in one button push. (hmmm...I thought that was a feature in the past...?)]

Resources