- Ruby Version 2.7.2
- Rspec Version 3.12.0
So I'm currently working through App Academy Open and I'm at the point where we're creating a tic tac toe game. I've written out all my tests and they pass except for the last few.
I have successfully stubbed method calls in the past but for whatever reason, I'm not getting it to work here.
I have 3 classes Board, HumanPlayer, and Game. The method I am currently testing is #play from within the Gameclass:
def play
while @board.empty_positions?
puts @board.print
position = @current_player.get_position
@board.place_mark(position, @current_player.mark)
if @board.win?(@current_player.mark)
puts "Player #{@current_player.mark} wins!"
return
else
switch_turn
end
end
puts "The game ended in a draw!"
end
Here is what my test looks like:
RSpec.describe Game do
let(:game) { Game.new(:X, :O) }
# ...
describe "#play" do
before :each do
@board = game.instance_variable_get(:@board)
@board.place_mark([0, 0], :X)
@board.place_mark([0, 1], :O)
@board.place_mark([0, 2], :X)
@board.place_mark([1, 0], :O)
@board.place_mark([2, 0], :X)
@board.place_mark([1, 1], :O)
@board.place_mark([2, 2], :X)
end
it "should call Board#place_mark" do
@current_player = game.instance_variable_get(:@current_player)
allow(@current_player).to receive(:get_position).and_return([1, 2])
expect(@board).to receive(:place_mark)
game.play
end
end
end
Here is the HumanPlayer#get_position method:
def get_position
puts "Player #{@mark}, enter two numbers representing a position in the format `row col`"
position = gets.chomp.split(" ")
if position.length != 2 || # not 2 characters
position.any? { |n| n.to_i.to_s != n } # not all numeric
raise "Invalid Position"
end
position.map(&:to_i)
end
Here is the Board#place_mark method:
def place_mark(position, mark)
raise "Placement Invalid" if !valid?(position) || !empty?(position)
row = position[0]
col = position[1]
@grid[row][col] = mark
end
So whenever I run the tests I always get the error:
Game Instance Methods #play should call Board#place_mark
Failure/Error: position = gets.chomp.split(" ")
Errno::ENOENT:
No such file or directory @ rb_sysopen - spec/2_game_spec.rb:85
# ./lib/human_player.rb:11:in `gets'
# ./lib/human_player.rb:11:in `gets'
# ./lib/human_player.rb:11:in `get_position'
# ./lib/game.rb:20:in `play'
# ./spec/2_game_spec.rb:92:in `block (4 levels) in <top (required)>'
I believe I'm stubbing the HumanPlayer.get_position method to return [1, 2] when called but for whatever reason, the Board.place_mark method does not successfully place the piece on the board and thus, the HumanPlayer.get_position gets called again because of the loop and when it hits the gets call, it produces that error output.
I've tried stubbing the gets call with this:
it "should call Board#place_mark" do
@current_player = game.instance_variable_get(:@current_player)
allow(@current_player).to receive(:gets).and_return("1 2")
expect(@board).to receive(:place_mark)
game.play
end
I also tired allow_any_instance_of(HumanPlayer) but it just prints the board in an endless loop:
it "should call Board#place_mark" do
@current_player = game.instance_variable_get(:@current_player)
allow_any_instance_of(HumanPlayer).to receive(:get_position).and_return([1, 2])
expect(@board).to receive(:place_mark)
game.play
end
This is my first question on SO, so if there is anything I need to add please let me know. Thanks in advance.
If you can't figure out the
Errno::ENOENTerror, you might try a slightly different design, which I think is easier to stub.