Home > front end >  Rspec test fails
Rspec test fails

Time:02-03

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
  • Related