4

I'm trying to understand what the best way to test a while loop in rspec would be.

I allow the user to enter the type of game he/she would like to play.

def get_action
    gets.strip.downcase
end

def type_of_game
    puts "Enter the type of game you would like to play (human v. human, computer v. computer, or human v. computer):"
    gametype = get_action
    until (gametype == "human v. human" || gametype == "computer v. computer" || gametype == "human v. computer")
        puts "Please enter a valid game"
        gametype = get_action
    end
    return gametype
end

Currently I have this in rspec, but it results in an infinite loop

require "minitest/spec"  
require "minitest/autorun"


describe Game do 
   before :each do
    @game = Game.new    
   end


   it "should prompt to user to enter their gametype again if not human v. human, computer v. computer, or human v. compouter" do
      def @game.get_action; "human v. machine" end
      expect(@game.type_of_game).to eql("Please enter a valid game")
   end

Thanks for your help

avni510
  • 43
  • 3

2 Answers2

3

what is happening is that your method get_action has been stubbed to continuously return an invalid response. (good work on that!)

So your type_of_game method will repeatedly call get_action, over and over and over again.

What you want to test for is that your type_of_game method correctly sent a message to the user.

To do this in rspec (3.0) you can use the output matcher like this:

expect { @game.type_of_game }.to output("Please enter a valid game").to_stdout

See this SO answer if you are not using rspec 3.0 Testing STDOUT output in Rspec

Community
  • 1
  • 1
Mitch VanDuyn
  • 2,838
  • 1
  • 22
  • 29
3

I would re-write it as follows as it allows us to stub loop and yield (thus avoiding the infinite loop issue you're having). One caveat of this approach is that you will receive the gametype 'human v. machine' as it yields after one iteration.

Class

class Game
  def get_action
    gets.strip.downcase
  end

  def type_of_game
    puts 'Enter the type of game you would like to play (human v. human, computer v. computer, or human v. computer):'
    gametype = get_action
    loop do
      break if gametype == 'human v. human' || gametype == 'computer v. computer' || gametype == 'human v. computer'
      puts 'Please enter a valid game'
      gametype = get_action
    end

    gametype
  end
end

Rspec (3.3.0)

require_relative 'path/to/game'

describe Game do
  subject { Game.new }

  it 'prompts the user to enter their gametype again if it is incorrect' do
    allow(subject).to receive(:gets).and_return('human v. machine')
    allow(subject).to receive(:loop).and_yield

    expect { subject.type_of_game }
      .to output(/Please enter a valid game/)
      .to_stdout
  end

  it 'does not prompt the user to enter their gametype if it is correct' do
    allow(subject).to receive(:gets).and_return('human v. human')

    expect { subject.type_of_game }
      .to_not output(/Please enter a valid game/)
      .to_stdout
  end

  it 'returns the specified gametype if valid' do
    allow(subject).to receive(:gets).and_return('human v. human')

    expect(subject.type_of_game).to eq('human v. human')
  end
end

The reason I'm using a regex matcher (//) is because stdout also includes Enter the type of game you would like to play (human v. human, computer v. computer, or human v. computer): which we do not care about.

Nabeel
  • 2,272
  • 1
  • 11
  • 14