as_null_object not passing with two inputs - ruby

I'm working through the RSpec Book, and I have the following test code:
require 'spec_helper'
module Codebreaker
describe Game do
describe "#start" do
let(:output) { double('output').as_null_object }
let(:game) { Game.new(output) }
it "sends a welcome message" do
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start
end
it "prompts for the first guess" do
output.should_receive(:puts).with('Enter guess:')
game.start
end
end
end
end
which corresponds to the following code:
module Codebreaker
class Game
def initialize(output)
#output = output
end
def start
#output.puts 'Welcome to Codebreaker!'
#output.puts 'Enter a guess:'
end
end
end
Since I've set :output up as a double.as_null_object, I expect it to ignore any arguments/methods it is not expecting. For the first test (sends a welcome message), that's what it does, and it passes. The second test, however, is giving me this error:
Failure/Error: output.should_receive(:puts).with('Enter guess:')
Double "output" received :puts with unexpected arguments
expected: ("Enter guess:")
got: ("Welcome to Codebreaker!"), ("Enter a guess:")
# ./spec/codebreaker/game_spec.rb:16:in `block (3 levels) in <module:Codebreaker>'
Why is the double returning both "Welcome to Codebreaker!" and "Enter a guess" when I have explicitly told it to only expect "Enter a guess:", and how can I fix this while maintaining this same setup/structure?

The second case is failing because you have a typo in your expectation. You meant Enter a guess: instead of Enter guess:.

Unfortunately, rspec is very picky about wording on strings. In your start method you wrote "Enter guess" instead of "Enter a guess:".
It's important to follow the wording to a T, when you start having to raise an error, rspec gives you a very nasty response.
Good luck! Rspec is a great tool as you get further into it.

Related

NoMethodError: undefined method `chomp' for nil:NilClass while running Ruby Rspec on gets.chomp

This is my first post here. I'm fairly new to Ruby, especially RSpec and have been running into an issue. I have written a method that uses gets.chomp to receive a player input. However I have this method called in another method
def prompt_move
loop do
#move = gets.chomp.to_i
return move if valid_move?(move)
puts "Invalid input. Enter a column number between 1 and 7"
end
end
def valid_move?(move)
#move.is_a?(Integer) && #move.between?(1, 7)
end
def play_round
print_board
prompt_player
#move = prompt_move
end
Here is the code for my RSpec tests:
describe ConnectFour do
subject(:game) { described_class.new }
let(:player){ double(Player) }
describe '#prompt_move' do
context 'move is a valid input' do
before do
allow(game).to receive(:gets).and_return('3\n')
end
it 'returns move and stops the loop' do
error_message = 'Invalid input. Enter a column number between 1 and 7'
expect(game).to_not receive(:puts).with(error_message)
game.prompt_move
end
end
context 'when given one invalid input, then a valid input' do
before do
letter = 'a'
valid_input = '1'
allow(game).to receive(:gets).and_return(letter, valid_input)
end
it 'completes loop and displays error message once' do
error_message = 'Invalid input. Enter a column number between 1 and 7'
expect(game).to receive(:puts).with(error_message).once
game.prompt_move
end
end
end
If I remove the #prompt_move method from #play_round the tests pass without any issue. However when I try to call it from within #play_round it gives me
NoMethodError:
undefined method `chomp' for nil:NilClass
I have been struggling to figure out what is causing this error so any suggestions would be greatly appreciated!
You're executing code in your class file.
new_game = ConnectFour.new
new_game.play_game
This will run every time you load the file, like when you're testing it. It will prompt for input and run gets. What it's getting is the code of the test file (for some rspec reason). That's invalid, so it keeps running gets until eventually there is no more input and gets returns nil.
Remove that from your class file. Code like that should be in a separate file which requires the class.

rspec fails even when variables should match

Methods in my class:
def get_class_info
#class_name = get_class_name
end
def get_class_name
puts "Enter the name for your class. Enter 'done' to return to the main prompt."
input = gets.chomp
if input.valid_class_name?
class_name = input.titleize
elsif input.downcase == "done"
run
else
puts "Invalid class name."
get_class_name
end
class_name
end
My test:
it "stores the name in the #class_name property" do
expect(cli.class_name).to eq('Song')
allow(cli).to receive(:gets) {'song'}
cli.get_class_info
end
It fails, saying it expected "Song" but got nil.
If I comment out the expect line and add a pry to the end of the test, querying cli.class_name returns "Song"!!!
Why is this test failing??
Update: Okay, moving the expect line to the bottom of the test apparently solved it. But I'm confused because usually the expectation goes before the actual method call. Am I wrong? What am I missing here?

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

Using RSpec to test user input with gets

I'm new to Unit Testing using RSpec and Ruby and I have a question on how to test if my code is using the gets method, but without prompting for user input.
Here is the code I'm trying to test. Nothing crazy here, just a simple one liner.
my_file.rb
My_name = gets
Here's my spec.
require 'stringio'
def capture_name
$stdin.gets.chomp
end
describe 'capture_name' do
before do
$stdin = StringIO.new("John Doe\n")
end
after do
$stdin = STDIN
end
it "should be 'John Doe'" do
expect(capture_name).to be == 'John Doe'
require_relative 'my_file.rb'
end
end
Now this spec works, but when I run the code it prompts for user input. I don't want it to do that. I want to simply test if the gets method is being called and possibly mock the user input. Not to sure how to achieve this in RSpec. In Python I would utilize unittest.mock is there a similar method in RSpec?
Thanks in advance!
Here's how you could stub gets with your return value.
require 'rspec'
RSpec.describe do
describe 'capture_name' do
it 'returns foo as input' do
allow($stdin).to receive(:gets).and_return('foo')
name = $stdin.gets
expect(name).to eq('food')
end
end
end
Failures:
1) should eq "food"
Failure/Error: expect(name).to eq('food')
expected: "food"
got: "foo"
(compared using ==)
To test if something is being called (such as a function) you can use expect($stdin).to receive(:gets).with('foo') to ensure it is being called (once) with the right args. The expectation line in this scenario has to go before name = $stdin.gets.

Cucumber test double: scenario failing but its steps passing

I'm learning Rspec + Cucumber with The RSpec Book. I'm just at the beginning, while developing a Codebreaker game.
In it, there is a feature "Codebreaker starts game" that represents simply a user typing a command in the shell and getting two responses: "Welcome to Codebreaker!" and "Enter a guess:". Here it is how the feature looks like:
Feature: code-breaker starts game
As a code-breaker
I want to start a game
So that I can break the code
Scenario: start game
Given I am not yet playing
When I start a new game
Then I should see "Welcome to Codebreaker!"
And I should see "Enter a guess:"
As the output is used by the cucumber script, the book is creating a mock object output which is expecting to receive the puts message with Welcome to Codebreaker! and Enter a guess: argument. Here it is how it looks in the step definitions:
#the mock object
class Output
def messages
#messages ||= []
end
def puts(message)
messages << message
end
end
def output
#output ||= Output.new
end
Given /^I am not yet playing$/ do
end
When /^I start a new game$/ do
game = Codebreaker::Game.new(output)
game.start
end
Then /^I should see "([^"]*)"$/ do |message|
output.messages.should include(message)
end
Ok, up till now no problem.
Doing this exercise, I remembered to had read before that rspeck doubles framework could be used inside cucumber, so I thought I could clean it a little bit.
First, I have included rspeck doubles framework in support/env.rb:
require 'cucumber/rspec/doubles'
And then I have changed the step definitions:
Given /^I am not yet playing$/ do
end
When /^I start a new game$/ do
#output = double('output').as_null_object #the mock object
game = Codebreaker::Game.new(#output)
game.start
end
Then /^I should see "([^"]*)"$/ do |message|
#output.should_receive(:puts).with(message)
end
The strange think is that now, when I execute the feature with cucumber, in the summary I get all 4 steps passing but not the whole scenario. How is it possible? What is it happening? Here it is the output I get from the command line:
Feature: code-breaker starts game
As a code-breaker
I want to start a game
So that I can break the code
Scenario: start game # features/codebreaker_starts_game.feature:6
Given I am not yet playing # features/step_definitions/codebreaker_steps.rb:1
When I start a new game # features/step_definitions/codebreaker_steps.rb:4
Then I should see "Welcome to Codebreaker!" # features/step_definitions/codebreaker_steps.rb:10
And I should see "Enter a guess:" # features/step_definitions/codebreaker_steps.rb:10
(Double "output").puts("Welcome to Codebreaker!")
expected: 1 time
received: 0 times (RSpec::Mocks::MockExpectationError)
/home/a_user/www/codebreaker/features/step_definitions/codebreaker_steps.rb:11:in `block in <top (required)>'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/error_generator.rb:80:in `__raise'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/error_generator.rb:39:in `raise_expectation_error'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/message_expectation.rb:251:in `generate_error'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/message_expectation.rb:207:in `verify_messages_received'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/method_double.rb:117:in `block in verify'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/method_double.rb:117:in `each'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/method_double.rb:117:in `verify'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/proxy.rb:88:in `block in verify'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/proxy.rb:88:in `each'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/proxy.rb:88:in `verify'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/methods.rb:116:in `rspec_verify'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/space.rb:11:in `block in verify_all'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/space.rb:10:in `each'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks/space.rb:10:in `verify_all'
/var/lib/gems/1.9.1/gems/rspec-mocks-2.10.1/lib/rspec/mocks.rb:19:in `verify'
/var/lib/gems/1.9.1/gems/cucumber-1.1.9/lib/cucumber/rspec/doubles.rb:12:in `After'
Failing Scenarios:
cucumber features/codebreaker_starts_game.feature:6 # Scenario: start game
1 scenario (1 failed)
4 steps (4 passed)
0m0.009s
When you set an expectation like should_receive, you're specifying that a some point in the future the specified method should be called - anything that has happened previously is ignored (or else it would be should have_received or something like that in the past tense).
In your code you're setting up the expectation in your Then step but the method gets called in your When step (i.e. before), so at that point no expectation has been setup. Your double is setup to allow any method to be called, so you get no errors, but when spec checks at the end whether all expectations have been satisfied it will say no and raise an exception

Resources