Harry B.
Harry B.

Reputation: 421

rspec test if method is called

I can't figure out how to write a test that will work, that also doesn't run anything in my script in the CL. When @counter is 9, it is supposed to trigger #tie_game? but it does not work. @counter is initialized to 0 and is incremented by one every time a player puts an X or an O on the game board. How can I test that once @counter is 9 that it calls tie_game? and that when it is less than 9 it will not?

def initialize(player1, player2)
    @player1 = player1
    @player2 = player2
    @rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

    @wins = [1, 2, 3], [4, 5, 6], [7, 8, 9], 
        [1, 4, 7], [2, 5, 8], [3, 6, 9],
        [1, 5, 9], [3, 5, 7]

    @selected_numbers = []
    @counter = 0
end

def increment_counter
    @counter += 1

    if @counter == 9
        tie_game?
    end
end

def tie_game?
    puts "The game was a tie. Nicely played #{@player1} & #{@player2}!"
    play_again  
end

I tried this: Can you explain why it does not work though?

describe "increment_counter" do
    context "counter equals 9" do
        it "calls tie_game?" do
            game.instance_variable_set(:@counter, 9)
            allow(game).to receive(:increment_counter)
            #expect(STDOUT).to receive(:puts).with("The game was a tie. Nicely played #{@player1} & #{@player2}!")
            expect(game).to receive(:tie_game?)
        end
    end
    context "counter is less than 9" do
        it "does not call tie_game?" do
            game.instance_variable_set(:@counter, 4)
            game.increment_counter
            expect(game).not_to receive(:tie_game?)  
        end
    end
end

Upvotes: 7

Views: 14274

Answers (2)

JasonK
JasonK

Reputation: 545

RSpec Mocks do this for you.

describe TicTacToe do
  let(:game) { TicTacToe.new }

  context "after fewer than 9 moves" do
    it "does not call tie_game?" do
      expect(game).not_to receive(:tie_game?)
      8.times { game.increment_counter }
    end
  end

  context "after 9 moves" do
    it "calls tie_game?" do
      expect(game).to receive(:tie_game?)
      9.times { game.increment_counter }
    end
  end
end

See https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/expecting-messages for more details.

As a side note, tie_game? probably shouldn't have a question mark after it. tie_game? suggests that it's checking to see if the game is tied.

Upvotes: 9

Aetherus
Aetherus

Reputation: 8898

This is an interesting question. My approach is monkey-patching the class, redefine the method tie_game? that makes some side effect which can be detected by the test.

Since I don't see your class name, let's just call it Foo.

First, create a module, say TrackMethods

module TrackMethods
  # A global variable that stores how many times a method is called.
  $method_calls = Hash.new(0)

  def self.[](*method_names)
    Module.new do
      method_names.each do |name|
        define_method name do |*args, &block|
          $method_calls[name] += 1
          super(*args, &block)
        end
      end
    end
  end
end

Then in spec_helper.rb

Foo.prepend TrackMethods[:tie_game?]

After that, in your tests, you can

expect($method_calls[:tie_game?]).to be > 0

Upvotes: 0

Related Questions