Making sense of the need for as_null_object in RSpec - ruby

While working my way through The RSpec Book, I came across the as_null_object method. I don't understand the explanation the book provides as to why it's needed:
... the simplest way is to tell the double output to only listen for the messages we tell it to expect and ignore any other messages.
But why does the example code fail? When we call double('output') in each example, are we not creating a new double object for each example and sending it a single message?
What I would like is an deeper explanation (than the book) of why the example code fails, and how as_null_object addresses the issue.
it "sends a welcome message" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start
end
it "prompts for the first guess" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Enter guess:')
game.start
end
The book tries to explain the reason for the error in an earlier section, but again I don't understand the explanation.
We've told the double in the first example to expect puts "Welcome to Codebreaker!" and we've satisfied that requirement, but we've only told it to expect "Welcome to Codebreaker!" It doesn't know anything about "Enter guess:"
Similarly, the double in the second example expects "Enter guess:" but the first message it gets is "Welcome to Codebreaker".

When you create a double with output = double('output') and then pass it to the new game in Game.new(output), that double will receive every message that it is passed in the codebreaker game code. You haven't included it, but the start method has the following code:
module Codebreaker
class Game
...
def start
#output.puts 'Welcome to Codebreaker!'
#output.puts 'Enter guess:'
end
end
end
Here, remember that the double output has been assigned to the instance variable #output in the game's initialize method, so in each spec it is called with two messages, first with 'Welcome to Codebreaker!', then with 'Enter guess:'.
Without as_null_object, the output double will fail when it receives anything other than what it expects, i.e. in the first spec anything other than 'Welcome to Codebreaker!' and in the second spec anything other than 'Enter guess:'. By using as_null_object on the double, you tell it to sit and wait and ignore anything other than what it expects. This avoids the problem above.
Hope that helps.

I agree the explanation is not crystal clear. Here is what I understand it to be, perhaps that in combination with shioyama's answer will make this click.
When you are creating specs, you are saying the output will include the expected phrases, but not that it will include ONLY the expected phrase. So lets say you got around the error by putting all of the expectations in one example, like this:
it "gets expected output" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Welcome to Codebreaker!')
output.should_receive(:puts).with('Enter guess:')
game.start
end
This will pass. The problem is, if you decide later on to have the game start by saying "Hi Joe!", it will then fail. Then you will have to go back and fix your specification when in reality, it is meeting the specification already. You need a mechanism for the output to react to unexpected input with no behavior. That way, you can have specific examples of output that are expected without them failing when something unexpected appears. This seems like very basic programming and unit test assertion, but in RSpec they implement it in this manner that might seem unclear to you and I. The phrase "null object" carries a lot of baggage, and doesn't seem to describe what it is doing, but it is an implementation of the null object pattern (http://en.wikipedia.org/wiki/Null_Object_pattern).

Related

How can I stop Ruby if/elsif statement from looping?

I am teaching myself Ruby by making a small game in order to test out how I feel about the language. My code was going rather smoothly until I encountered a problem in which the first decision of the game loops instead of progressing forwards.
This code is what I have been using for a short part in the Exposition of my game.
def getup_or_sleep?
puts"Cole";
puts"----";
puts"Will you get up or go back to sleep?";
decision = gets
if decision == "sleep";
puts"Cole";
puts"----";
puts"You decide to go back sleep. It is far too early.";
elsif decision == "get up";
Exposition.stretch
else;
Exposition.getup_or_sleep?
end
This is the expected result I was hoping to achieve:
Cole
Will you get up or go back to sleep?
If player chooses 'sleep'
1)Cole
You decide to go back to sleep, it is far too early.
*I plan to make a new method to direct the user to, but I first want to fix this issue.
**if player chooses 'get up'
->>> to stretch method which is inside of the same class.
I'm new to coding so I may be confused on a few things. Thanks in advance! =)
Your method calls itself recursively because it all conditions fail, and it always falls back to the else branch.
This happens because gets reads input from the user and returns the input including the invisible newline character that is added when the user hits the enter key. But your conditions do not include such new line characters.
A common Ruby idiom is to call gets.chomp to get user input, where the chomp removes the newline character from the input.
Just change this line decision = gets to
decision = gets.chomp
to fix your issue.
Apart from that, your code isn't following Ruby idioms, for example, Ruby does not require a ; at the end of a line, or you usually add a whitespace between a method name and its argument, like in puts "Cole". Therefore, I suggest formatting your code like this:
def getup_or_sleep?
puts 'Cole'
puts '----'
puts 'Will you get up or go back to sleep?'
decision = gets.chomp
if decision == 'sleep'
puts 'Cole'
puts '----'
puts 'You decide to go back sleep. It is far too early.'
elsif decision == 'get up'
Exposition.stretch
else
Exposition.getup_or_sleep?
end
end
Or with a case block and some duplication extracted into a method:
def greeting
puts 'Cole'
puts '----'
end
def getup_or_sleep?
greeting
puts 'Will you get up or go back to sleep?'
case gets.chomp
when 'sleep'
greeting
puts 'You decide to go back sleep. It is far too early.'
when 'get up'
Exposition.stretch
else
Exposition.getup_or_sleep?
end
end

RSpec: Using a method stub to test interactive user input

I'm new to both Ruby and RSpec, and have already spent hours trying to write the first steps for testing an interactive tic tac toe program, but haven't located any useful answers to the error I'm getting:
> bundle exec rspec --format documentation
Do you want to play tic-tac-toe? (y/n)
An error occurred while loading ./spec/tic_tac_toe_spec.rb.
Failure/Error: choice = gets.chomp.downcase
Errno::ENOENT:
No such file or directory # rb_sysopen - --format
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `gets'
# ./lib/tic_tac_toe.rb:70:in `start_game'
# ./lib/tic_tac_toe.rb:144:in `<top (required)>'
# ./spec/tic_tac_toe_spec.rb:1:in `require'
# ./spec/tic_tac_toe_spec.rb:1:in `<top (required)>'
No examples found.
Finished in 0.0004 seconds (files took 0.08415 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
When you start the game, it prompts whether you want to play (y/n). I've already attempted different variations of using mocking and stubs but always get this same error. I imagine I'm missing something very basic but cannot see it.
Question: how would one adequately address (using RSpec) this first prompt of the game?
The game uses an independent method (start_game) but not a class to start. I'm looking for some way mock or stub this with some default input (like y or n) as I want the tests to be automated. All the examples I see use a class to start a program, however (rather than a stand-alone method like here).
Currently in spec/tic_tac_toe_spec.rb there are some let keywords for :output and :game which I found from an example somewhere but obviously this doesn't work.
Edit I want to test the following method. The RSpec code keeps choking on the choice = gets.chomp.downcase line.
def start_game
puts "Do you want to play tic-tac-toe? (y/n)"
choice = gets.chomp.downcase
unless ["y","n"].include?(choice)
abort("Please answer with y or n.")
end
case choice
when "y"
puts "Ok, let's start!"
b = Board.new
puts "Enter name of player 1 (O)"
player1name = gets.chomp
player1 = Player.new(player1name)
puts "Now enter name of player 2 (X)"
player2name = gets.chomp
player2 = Player.new(player2name)
play_game(player1, player2, b)
when "n"
puts "Your loss."
end
end #start_game
You are receiving the error because you are passing the --format documentation as a parameter to RSpec.
gets reads the ARGV that have been passed. This also includes the RSpec parameters. You should use STDIN.gets so that you only read standard input and not the parameters.
You can read more about in this question:
https://stackoverflow.com/a/19799223/6156030
A simple approach could be:
it 'works' do
allow($stdin).to receive(:gets).and_return('y')
expect { start_game } # (Or however you run it!)
.to output("Ok, let's start!")
.to_stdout
end
You can also specify multiple return values for gets, which will be used sequentially as the test runs.
The alternative approach you seem to have started (but not fully implemented) is to inject an explicit output (and, presumably, one day an input) object into the game.
This would indeed be a cleaner approach from the purist's point of view - i.e. not stubbing the global objects like $stdin - but is probably overkill for your initial version of the code. Unless you plan doing something fancy like running parallel specs, I wouldn't worry about that.
Edit: After looking at your actual code in more detail, I see the problem. You're defining global methods which are doing multiple things and are tightly coupled. This makes writing tests much harder!
Here I have added a working test example:
https://github.com/tom-lord/tic_tac_toe_rspec/commit/840df0b7f1380296db97feff0cd3ca995c5c6ee3
However, going forward in order to simplify this my advice would be to define all each method within an appropriate class, and make the code less procedural. (i.e. Don't just make the end of one method call the next method, in a long sequence!) This refactoring is perhaps beyond the scope of a StackOverflow answer though.
You need to stub and mock gets method in your specs:
yes_gets = double("yes_gets")
allow($stdin).to receive(:gets).and_return(yes_gets)
Which then you can make it respond to #chomp:
expect(yes_gets).to receive(:chomp).and_return('Y')
You can cover the similar method call for downcase by returning this double object itself.
You can also do the similar work for mock object for your 'N' case where you'd expect game to exit when player inputs an N(No):
no_gets = double("no_gets")
allow($stdin).to receive(:gets).and_return(no_gets)
expect(no_gets).to receive(:chomp).and_return('N')

What is this Rspec syntax exactly asking for?

TL;DR I am going to a bootcamp next year and one their assessments to get in is learning ruby. I have a background in JS and am very new to ruby. I have this assessment in which case I am not familiar with what the test wants me to do. Below is the test case(Rspec) and right below that is my answer.
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
expect(result).to eq("olleh")
end
it "reverses each word in the string returned by the default block" do
result = reverser do
"hello dolly"
end
expect(result).to eq("olleh yllod")
end
end
This is my answer code:
def reverser sentence
words = sentence.split(" ")
result = []
words.length.times do |i|
result.push(yield(words[i]))
end
result.join(" ")
end
reverser("hello dolly") {|n| n.reverse} #=> 'olleh yllod'
As I mentioned above I am new to ruby and the idea of yielding is like a callback function for JS. I am having a hard time figuring out what expected code the test case wants me to write. It says that 'it reverses each word in the string returned by the default block' from the statement I just created a block outside of the function where the words are being reversed. I appreciate the help and guidance from whoever can give advice.
This is really more about TDD than about Ruby. The approach would be much the same in any other language as well.
The point of TDD is to write the simplest test that could possibly fail, and then write the simplest code that could possibly change the error message. Step #1 is already provided to you in this example, so this is really about step #2: write the simplest code that could possibly change the message.
Let's run the tests! The message says:
NoMethodError:
undefined method `reverser' …
What's the simplest code that could possibly change a message that says a method doesn't exist? Well, make the method exist, of course!
def reverser; end
Now, the message is:
expected: "olleh"
got: nil
Okay, so it expected us to return the string 'olleh', but we actually returned nil. That's easy:
def reverser; 'olleh' end
Great! The first test passes. But the second still fails. It says:
expected: "olleh yllod"
got: "olleh"
Hmm … apparently, it is not enough to just return something statically. We have to return something different every time. The obvious source would be an argument, but our method doesn't have any parameters. Or does it? Yes! In Ruby, all methods have an implicit block parameter! You can evaluate it using the yield keyword, so let's try that:
def reverser; yield end
Damn! We're back to two errors! That was a step backwards. Or was it? Let's look at the first error message:
expected: "olleh"
got: "hello"
Do you notice something? The expected value is exactly the reverse of the value we are currently returning. So, all we need to do is reverse the return value:
def reverser; yield.reverse end
Hooray! We're back to 1 error:
expected: "olleh yllod"
got: "yllod olleh"
Again, can you spot what is happening? The order of the words is reversed! We need to separate the words and then reverse them, then put them back together:
def reverser; yield.reverse.split.reverse.join end
So close!
expected: "olleh yllod"
got: "ollehyllod"
We just need to put the space back in:
def reverser; yield.reverse.split.reverse.join(' ') end
Yippieh!
2 examples, 0 failures
The important thing is: at no point did we actually have to think. Every step of the way, the tests told us what to do, what to do next, and when we were done. That's what TDD is about: the tests drive the development.
I think the point of the question should be to explain about RSpec and TDD if you're going on a bootcamp. I think giving you the answer to the problem is only part of what you need to know in this case. So...
One of the principles of TDD is to write the least amount of code to get the tests to pass. If I go back to the start.
You have written a reverser method that takes sentence as an argument:
def reverser sentence
'olleh'
end
Run the test and the test should fail with an error: wrong number of arguments. Remove the argument and try that and run RSpec again:
def reverser
'olleh'
end
The first test should pass, but it will fail the second test as we've hard coded the return value of olleh. Clearly it's no good returning a hard coded value — it just helped the test to pass — so I need to figure out how to yield a return value.
def reverser
yield.reverse
end
And this perhaps will get the second test to pass...
This is the guiding principle of TDD, take small steps and get your code to pass. Once it's passing then go back and refactor and improve your code.
As you probably know already TDD is invaluable for writing good quality code - it makes writing code fun, it helps with refactoring.
I found a page with some resources and I would recommend codeschool but they've retired their course which is a pity but there is a PluralSight TDD course that might be useful and also there is a Udemy course on TDD that might be useful too in giving you a head start.
yield means "execute the code inside the block and give me the result". Your reverser method can be simply written like this:
def reverser
yield.split.map(&:reverse).join(' ') if block_given?
end
result = reverser do
'hello world'
end
puts result
# => olleh dlrow
Ruby automatically returns the last thing called as the return value, so the return value of the block given to reverser is hello world which is what will be assigned to yield.
You could check the documentation for block_given? here. Take a look at this answer for deeper explanation of blocks

Can variables be passed after a do/end block?

I am working with a custom testing framework and we are trying to expand some of the assert functionality to include a custom error message if the assert fails. The current assert is called like this:
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end
and we want something with the functionality of:
assert_compare(first_term, :neq, second_term, error_message) do
puts 'foobar'
end
so that if the block fails the error message will describe the failure. I think this is ugly, however, as the framework we are moving away from did this and i have to go through a lot of statements that look like:
assert.compare(variable_foo['ARRAY1'][2], variable_bar['ARRAY2'][2], 'This assert failed because someone did something unintelligent when writing the test. Probably me, since in am the one writing this really really long error statement on the same line so that you have to spend a quarter of your day scrolling to the side just to read it')
This type of method call makes it difficult to read, even when using a variable for the error message. I feel like a better way should be possible.
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end on_fail: 'This is a nice error message'
This, to me, is the best way to do it but i don't know how or if it is even possible to accomplish this in ruby.
The goal here is to make it as aesthetic as possible. Any suggestions?
You could make on_fail a method of whatever assert_compare returns and write
assert_compare(first_term, :neq, second_term) do
puts 'foobar'
end.on_fail: 'This is a nice error message'
In short, no. Methods in ruby take a block as the final parameter only. As Chuck mentioned you could attempt to make the on_fail method a method of whatever assert_compare returns and that is a good solution. The solution I've come up with is not what you are looking for, but it works:
def test block, param
block.call
puts param
end
test proc { puts "hello"}, "hi"
will result in
"hello"
"hi"
What I've done here is create a Proc (which is essentially a block) and then passed it as a regular parameter.

Cucumber "puts" in After hook not outputting anything

In Cucumber, in my env.rb file, I have a before & after hook set up (well, a few of them, some linked to specific tags) but have found the after hooks don't output anything when I put a puts in them.
For example, this works:
Before do
puts "before the scenario"
end
but this doesn't:
After do
puts "after the scenario"
end
It seems that the after hooks do get run (as there's a particular line I'm having a problem with in the after hook & in trying to debug that, I've found this problem) but they're just not outputting anything.
All my searching has proved fruitless, can't find anyone else with similar problems. Can anyone tell if I'm doing something wrong?
Cucumber overrides the puts message in the RbWorld class so that anything written with puts gets properly broadcasted to all formatters. In the case of the pretty formatter, these go into a delayed_messages collection, until it calls print_messages, which it appears to do after each step name has been printed, presumably so that messages appear to be nested under the step in which they were generated.
For some reason there is no final call to print_messages in the pretty formatter, I'm not sure if it's an omission or deliberate since it would look less 'pretty' to have spurious messages in the output.
Interestingly if you add a second scenario, you'll see 'after the scenario' printed as the first message when the second scenario gets run, that's the delayed_messages collection in action.
In summary, you're doing nothing wrong, it's just how Cucumber hijacks the puts method. If you're not too bothered about these messages being nicely formatted, then you can just replace puts with STDOUT.puts to bypass Cucumber's formatting.
Before do
p "Go!"
puts "Go!"
end
After do
p "Stop!"
puts "Stop!"
$stdout.puts "Stop!"
end
output of this snippet may help to understand why 'puts' not working in After hook.

Resources