evkline
evkline

Reputation: 1511

How do I write RSpec to test my Poker game discard?

I'm using RSpec for the first time to create a Poker game with Card, Deck, Hand, Player, & Game classes. I've completed the Card, Deck, & Hand tests/classes, and I'm currently writing the Player test/class. Its gotten more difficult to rely on mocks/stubs as the dependencies have grown and I am running into an issue where my Player#discard method testing is throwing an error because RSpec is throwing this error:

1) Player#discard discards the chosen card
   Failure/Error: player.discard
      Double "hand" received unexpected message :delete_if with (no args)
    # ./lib/player.rb:19:in `block in discard'
    # ./lib/player.rb:18:in `each'
    # ./lib/player.rb:18:in `discard'
    # ./spec/player_spec.rb:34:in `block (3 levels) in <top (required)>'

I'm new to RSpec & not really sure what I'm doing wrong. Any help & advice on ways to avoid using the actual class objects in my specs to make them pass would be greatly appreciated. Here is my player_spec & Player class so far.

./spec/player_spec.rb

require 'rspec'
require 'player'

describe Player do
  subject(:player) { Player.new }

  its(:hand) { double("hand", :cards => []) }
  its(:pot)  { should eq 1000 }

  let(:ace_spades)    { double("card", :suit => :spades, :value => :ace) }
  let(:ace_clubs)     { double("card", :suit => :clubs, :value => :ace) }
  let(:king_spades)   { double("card", :suit => :spades, :value => :king) }
  let(:king_clubs)    { double("card", :suit => :clubs, :value => :king) }
  let(:queen_spades)  { double("card", :suit => :spades, :value => :queen) }
  let(:queen_clubs)   { double("card", :suit => :clubs, :value => :queen) }
  let(:jack_spades)   { double("card", :suit => :spades, :value => :jack) }
  let(:jack_clubs)    { double("card", :suit => :clubs, :value => :jack) }
  let(:ten_spades)    { double("card", :suit => :spades, :value => :ten) }
  let(:ten_clubs)     { double("card", :suit => :clubs, :value => :ten) }
  let(:nine_spades)   { double("card", :suit => :spades, :value => :nine) }
  let(:nine_hearts)   { double("card", :suit => :hearts, :value => :nine) }
  let(:nine_clubs)    { double("card", :suit => :clubs, :value => :nine) }
  let(:nine_diamonds) { double("card", :suit => :diamonds, :value => :nine) }

  let(:discard_hand)  do 
    double("hand", :cards => [nine_clubs, ace_spades, nine_hearts, ten_clubs, ten_spades])
  end
  let(:discard_input) { ["A","S"] }

  describe "#discard" do
    it "discards the chosen card" do
      player.hand.stub(:cards).and_return(discard_hand)
      player.stub(:get_input).and_return(discard_input)
      player.discard
      expect(player.hand.cards).to eq([nine_clubs, nine_hearts, ten_clubs, ten_spades])
    end
  end
end

./lib/player.rb

class Player
  INPUT_VALUES = { "2" => :deuce, "3" => :three, "4" => :four, 
               "5" => :five, "6" => :six, "7" => :seven, 
               "8" => :eight, "9" => :nine, "10" => :ten, 
               "J" => :jack, "Q" => :queen, "K" => :king, "A" => :ace }
  INPUT_SUITS  = { "S" => :spades, "H" => :hearts, "C" => :clubs, "D" => :diamonds }

  attr_accessor :pot, :hand

  def initialize
    @pot  = 1000
    @hand = Hand.new
  end

  def discard
    discards = get_input("Which card(s) would you like to discard?:")

    discards.each do |discard|
      self.hand.cards.delete_if do |card| 
        card.value == discard[0] && card.suit == discard[1]
      end
   end
 end

private

  def get_input(prompt)
    puts prompt
    input = gets.chomp
    parse_input(input)
  end

  def parse_input(input)
    value = input.scan(/^(?:[2-9]|10|[jqkaJQKA])/)
    suit = input.scan(/[shdcSHDC]$/)
    raise "Invalid Input" if values.empty? || suit.empty?

    (value[0].match(/[jqkaJQKA]/)) ? value[0].upcase! : value.map!(&:to_i)
    suit[0].upcase!

    value = INPUT_VALUES[value[0]]
    suit  = INPUT_SUITS[suit[0]]

    [value, suit]
  end
end

Upvotes: 0

Views: 263

Answers (1)

Peter Alfvin
Peter Alfvin

Reputation: 29419

You're getting the error you're getting because the code you're calling (i.e. discard) is invoking delete_if on one of your doubles for which you haven't set an expectation that would allow that. You should check out stub_chain as a means of achieving this.

More generally, though, I think you're getting into trouble because you're asking the Player discard method to implement the delete functionality by operating on the cards array directly rather than delegating discard to Hand. If you can simplify your code, it will simplify your tests.

Upvotes: 1

Related Questions