Undefined method error using a Proc with ruby, cucumber and rspec - ruby

Looks like my use of the proc maybe a bit off. I'm working on a tic-tac-toe game and using cucumber to test it's behavior. I've outlined the scenario that i want to fulfill and the step file that I'm using.
The Scenario,
Scenario: Making Bad Moves
Given I have a started Tic-Tac-Toe game
And it is my turn
And I am playing X
When I enter a position "A1" on the board
And "A1" is taken
Then computer should ask me for another position "B2"
And it is now the computer's turn
The step files say...
Given /^I have a started Tic\-Tac\-Toe game$/ do #
#game = TicTacToe.new(:player)
#game.player = "player" #
end
Given /^it is my turn$/ do #
#game.current_player.should eq "player"
end
Given /^I am playing X$/ do
#game = TicTacToe.new(:computer, :X)
#game.player_symbol.should eq :X
end
When /^"(.*?)" is taken$/ do |arg1|
#game.board[arg1.to_sy m] = :O **- # this is causing me to get a "undefined
method `[]=' for #
Given /^I am playing X$/ do
#game = TicTacToe.new(:computer, :X)
#game.player_symbol.should eq :X
end
My code that is attempting to satisfy the feature is:
def board
Proc.new do |get_player_move = :B2|
board_value = get_player_move
#board_locations[board_value]
end
I get this error: NoMethodError: undefined method[]=' for #`
Am i using the proc properly?

Your problem is that [] and []= are in fact different methods. When you type:
#game.board[arg1.to_sym] = :O
ruby reads it as:
#game.board.[]=(arg1.to_sym, :o)
and what you want is
#game.board.[](arg1.to_sym) = :O
To make sure ruby knows what you want do:
(#game.board[arg1.to_sym]) = :O
NOTE:
To be honest I am not sure why you are using Proc here at all, why not simple:
def board
#board_locations
end

Related

Why is my current stub in my RSpec test not properly stubbing the call to the method? Currently getting No such file or directory # rb_sysopen

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::ENOENT error, you might try a slightly different design, which I think is easier to stub.
class GameCLI
def gets
Kernel.gets
end
end
class HumanPlayer
def get_position
# ..
cli = GameCLI.new # or, pass cli instance as argument to e.g. get_position
position = cli.gets # ..
# ..
end
end
RSpec.describe HumanPlayer do
describe '#get_position' do
it '..' do
allow_any_instance_of(GameCLI).to receive(:gets).and_return('..')
# ..
end
end
end

RSpec: simulating user input (via gets) without the test prompting for it

I'm coding a game by taking a TDD first approach, and have gotten stuck because the test keeps stopping for user input (repo is here).
I want the test to simulate user input rather than prompting for it, as I've set up some let keywords and have tried to account for user input that comes in via gets.chomp.
Here is where the game prompts for user input:
game.rb
module ConnectFour
class Game
def start_game
puts 'Welcome to Connect Four.'
puts "Enter name of player 1 (red)"
player1name = gets.chomp
player1 = Player.new(player1name)
end
end
end
And here is the test code:
game_spec.rb
require 'spec_helper'
module ConnectFour
describe Game do
let(:game) { Game.new }
let(:player1name) { 'Bob' }
let(:player1) { Player.new(player1name) }
describe 'Instantiate game play objects' do
describe 'Create player 1' do
it 'Provide player 1 name' do
allow_any_instance_of(Kernel)
.to receive(:gets)
.and_return(player1name)
end
it 'Instantiate player 1' do
expect(player1.name).to eq player1name
end
end
end # describe 'Instantiate game play objects'
end # Describe 'Game'
end
So far I've tried encapsulating the gets.chomp in its own method as recommended here but this has no effect. I've also tried prefixing $stdin to gets.chomp statements in the Ruby code but yeah, that was pretty useless. I had asked a similar question here recently and thought I had understood how to simulate user input but obviously not... any help would be appreciated.
use allow_any_instance_of(Object) instead of Kernel. The module Kernel is included into Object. Kernel is not ever actually instantiated because it's a module.
kind of a small point, but it'd be more accurate if you stubbed gets to return a strinng ending in \n, otherwise you could remove the chomp from the tested functionn and the test will still pass
reproducable example
require 'rspec'
require 'rspec/expectations'
test_case = RSpec.describe "" do
it "" do
allow_any_instance_of(Object).to receive(:gets).and_return "something\n"
puts gets.chomp
end
end
test_case.run

Accessing instance variable array using IRB

I'm new to Ruby and am working on an exercise where a sports team (of up to 10 ppl) have to have at least 2 men and 2 women. I've created a Player class where the player's gender is determined and a Team class, where I add these players into an instance variable array of #team (which is created upon initialization of Team).
I have placed my full code towards the bottom of this request.
I'm hoping someone can please help me on the following:
(1) What do I type in IRB to be able to specifically recall/maniuplate the #team instance variable array (which stores all the players). I wish to later iterate over the array or extract the first item in the array (#team.first does not work)
(2) I'm having difficulty writing the code to determine if at least 2 male and female players are in the #team instance variable. The best code I came up in the Team class was the following - but it is reporting that #team is nilclass.
def gender_balance
#team.select{|player| player.male == true }
end
I have researched the internet and tried various combinations for an answer - with no success.
Directly below are the IRB commands that I have typed to create and add players to teams. Later below is my code for Team (which contains the method to assess whether it has the right gender mix) and Players.
irb(main):001:0> team = Team.new
=> #<Team:0x007fd8f21df858 #team=[]>
irb(main):002:0> p1 = Player.new
=> #<Player:0x007fd8f21d7b08 #male=true>
irb(main):003:0> p1.female_player
=> false
irb(main):004:0> p2 = Player.new
=> #<Player:0x007fd8f21bff58 #male=true>
irb(main):005:0> team.add_player(p1)
=> [#<Player:0x007fd8f21d7b08 #male=false>]
irb(main):006:0> team.add_player(p2)
=> [#<Player:0x007fd8f21d7b08 #male=false>, #<Player:0x007fd8f21bff58 #male=true>]
These two IRB lines are me trying unsucessfully to recall the contents of #team
irb(main):007:0> team
=> #<Team:0x007fd8f21df858 #team=[#<Player:0x007fd8f21d7b08 #male=false>, #<Player:0x007fd8f21bff58 #male=true>]>
irb(main):013:0> #team
=> nil
The code for both classes is below:
class Player
def initialize
#male = true
end
def female_player
#male = false
end
def male_player
#male
end
end
class Team
def initialize
#team = []
end
def add_player player
#team << player
end
def player_count
#team.count
end
def valid_team?
player_number_check
gender_balance
end
private
def player_number_check
player_count > 6 && player_count < 11
end
def gender_balance
#team.select{|player| player.male == true }
end
end
My github reference for this code is: https://github.com/elinnet/object-calisthenics-beach-volleyball-edition.git
Thank you.
Your Team class does not have an attribute for getting the #team instance variable.† So the only way you can extract its value is by using instance_variable_get:
irb(main):029:0> team = Team.new
=> #<Team:0x007fff4323fd58 #team=[]>
irb(main):030:0> team.instance_variable_get(:#team)
=> []
Please don't use instance_variable_get for actual production code though; it's a code smell. But for the purposes of inspecting instance variables in IRB, it's okay.
† You'd normally define one using either attr_accessor :team (read/write) or attr_reader :team (read-only) inside the class definition.

Struggling to write code and tests for my Tube System program, TDD using Rspec and Ruby

I am writing a small program for a train system.
I have a passenger, coach, train and station class (and thus, a spec test for each).
My test for my passenger class is as such:
let (:passenger) {Passenger.new}
it "should not be touched in to a station when initialized" do
expect(passenger.touchedin?).to be false
end
it "should be able to enter coach" do
coach = Coach.new
passenger.enter(coach)
expect{coach.to receive(:enter)}
end
it "should be able to alight coach" do
coach = Coach.new
passenger.alight(coach)
expect{coach.to receive(:alight)}
end
it "should be able to touch into station" do
station = Station.new
passenger.touchin(station)
expect{station.to receive(:touchin)}
end
it "should be able to touch out of station" do
station = Station.new
passenger.touchout(station)
expect{station.to receive(:touchout)}
end
end
And my passenger class is like this (at the moment :p):
class Passenger
def initialize
#touchedin = false
end
def enter(coach)
end
def touchedin?
#touchedin
end
def alight(coach)
end
def touchin(station)
end
def touchout(station)
end
end
I am unsure how to satisfy my tests, if my tests are even correct in the first place.
Any help is really appreciated!
You've not really said how you're modeling the relationship between coaches and passengers, but one way I could think of could be as follows. I'm just putting enough for the coach/passenger relationship (so nothing about touching in as this involves the station) - and I'm using minitest syntax, but I think you can get the idea of what's happening.
class Coach
def initialize
#passengers = []
end
...
end
class Passenger
def initialize
#touched_in = false
end
def alight(coach)
coach.passengers << self.uid # or self, if you want the whole passenger object available
end
...
end
coach = Coach.new
assert_empty coach.passengers
joe = Passenger.new
refute_includes coach.passengers, joe.uid # or joe
joe.alight(coach)
assert_includes coach.passengers, joe.uid # or joe

Embed RSpec test in a Ruby class

I often build little single-purpose Ruby scripts like this:
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
end
w = Widget.new
puts w.render_data(w.end_data)
__END__
data set to work on.
I'd like to include RSpec tests directly inside the file while I'm working on it. Something like this (which doesn't work but illustrates what I'm trying to do):
#!/usr/bin/env ruby
class Widget
def end_data
DATA.read
end
def render_data source_data
source_data.upcase
end
def self_test
# This doesn't work but shows what I'm trying to
# accomplish. The goal is to have RSpec run these type
# of test when self_test is called.
describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
end
end
w = Widget.new
w.self_test
__END__
data set to work on.
I understand this is not the normal way to work with RSpec and isn't appropriate in most cases. That said, there are times when it would be nice. So, I'd like to know, is it possible?
There are two things. First off rspec by default won't pollute the global namespace with methods like describe and so on. The second thing is that you need to tell rspec to run the specs after they've been declared.
If you change your self_test method to be
RSpec.describe "Widget" do
it "should render data properly" do
#w = Widget.new
expect(#w.render_data('test string')).to eq 'TEST STRING'
end
end
RSpec::Core::Runner.invoke
(having of course done require 'rspec' then that will run your specs).
The invoke methods exits the process after running the specs. If you don't want to do that, or need more control over where output goes etc. you might want to drop down to the run method which allows you to control these things.

Resources