I am trying to test #game_over?
method in which it calls #check_row
in Board
class using an instance_double
but the test fails. Here is the test:
describe '#game_over' do
subject(:game_over) { described_class.new }
let(:board) { instance_double(Board) }
context 'when #game_over is called' do
it 'calls #check_row in Board' do
game_over.game_over?
expect(board).to receive(:check_row)
#game_over.game_over?
end
end
end
I was expecting the #game_over?
to call #check_row
in Board
class but the test fails. Here is the method I am testing:
def game_over?
return true if @board.check_row || @board.check_column || @board.check_diagonal || @board.check_antidiagonal
false
end
Here is the failure message:
1) Game#game_over when #game_over is called calls #check_row in Board
Failure/Error: expect(board).to receive(:check_row)
(InstanceDouble(Board) (anonymous)).check_row(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
Here is my Game
#initialize
method:
def initialize
@board = Board.new
end
CodePudding user response:
The instance of Board
in your Game
class and the board mock in your test are different instances and therefore the test fails.
I suggest using dependency injection to be able to control the board instance in the Game
and change your initializer and your test like this:
# in the Game class
def initialize(board = nil)
@board = board || Board.new
end
# in your spec
describe '#game_over?' do
subject(:game) { described_class.new(board) } # inject the board stub here
let(:board) { instance_double(Board) }
before { allow(board).to receive(:check_row).and_return(true) }
it 'delegates to Board#check_row' do
game.game_over?
expect(board).to have_received(:check_row)
end
end
Note:
I would argue that the test in its current form doesn't add much value and that it tests an internal implementation detail that you should not really care about.
There is no benefit in testing that a specific method is called on an internal @board
object in the Game
. In fact, testing such internal behavior will make it more difficult to refactor the code later on when requirements changed or new features are implemented.
Instead, I suggest focusing on testing that the method returns the expected result under certain preconditions (but not if and how the result is received from another object).
In this example, I suggest not testing that board.check_row
was called, but that Board#game_over?
returns the expected result because of the call) like this:
# in the Game class
def initialize(board = nil)
@board = board || Board.new
end
# in your spec
describe '#game_over?' do
subject(:game) { described_class.new(board) } # inject the board stub here
let(:board) { instance_double(Board) }
context 'with the board row check returns true' do
before { allow(board).to receive(:check_row).and_return(true) }
it 'is game over' do
expect(game).to be_game_over
end
end
end