Cucumber "puts" in After hook not outputting anything - ruby

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.

Related

How to make ruby methods run before and after each [given, when, then] step?

I'm trying to get some easy-to-read time metrics for how long steps of each scenario take to run.
I have methods that insert rows into a logging table like this:
puts "#{Time.now} - Starting Given"
puts "#{Time.now} - Ending Given"
etc...
And I want them to be called automatically at the start and end of each step, but I can only find ways to run them before or after the entire scenario, rather than each step.
Is there a way to do this?
Ruby comes with a benchmarking module to handle this stuff pretty easily for you.
Edit to address the comments below
In Cucumber, there is a hook called Around that allows you to wrap your scenario however you wish. For example, straight from the docs:
Around('#fast') do |scenario, block|
Timeout.timeout(0.5) do
block.call #actually runs the scenario
end
end

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.

RSpec test for a module

I'm brand new to RSpec and TDD. I was wondering if someone might help me with creating a test well-suited for this Module:
module Kernel
# define new 'puts' which which appends "This will be appended!" to all puts output
def puts_with_append *args
puts_without_append args.map{|a| a + "This will be appended!"}
end
# back up name of old puts
alias_method :puts_without_append, :puts
# now set our version as new puts
alias_method :puts, :puts_with_append
end
I'd like for my test to check that the content from a 'puts' ends with "This will be appended!". Would that be a sufficient test? How would I do that?
The best tests test what you're trying to achieve, not how you achieve it... Tying tests to implementation makes your tests brittle.
So, what you're trying to achieve with this method is a change to "puts" whenever your extension is loaded. Testing the method puts_with_append doesn't achieve this goal... If you later accidentally re-alias that to something else, your desired puts change won't work.
However, testing this without using an implementation detail would be rather difficult, so instead, we can try to push the implementation details down to somewhere they won't change, like STDOUT.
Just the Test Content
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
Full Test
I'm going to turn this into a blog post within the next day or so, but I think you should also consider that you've got a desired result for one and many arguments, and your tests should be easy to read. The ultimate structure I'd use is:
require "rspec"
require "./your_extention.rb"
describe Kernel do
describe "#puts (overridden)" do
context "with one argument" do
it "should append the appropriate string" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
end
end
context "with more then one argument" do
it "should append the appropriate string to every arg" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
$stdout.should_receive(:write).with("TwoThis will be appended!")
puts("One", "Two")
end
end
end
end

Making sense of the need for as_null_object in RSpec

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).

Calling Block multiple times in Cucumber Around Hook (Ruby)

I'm trying to run a scenario several (30) times in order to get a nice statistical sample. However the block is only executing once; each subsequent time results in the scenario being called and not executing (although it says that the scenario did successfully complete with a time of around 5 ms).
Around('#mass_benchmark') do |scenario, block|
$seconds_taken = "SECONDS TAKEN NOT SET"
#time_array = []
30.times do
before_hook(scenario)
block.call
after_hook(scenario)
#time_array << $seconds_taken
end
write_time_array_to_file(#time_array, scenario_name)
end
The tag #mass_benchmark executes this block, as opposed to ~#mass_benchmark, which just executes the scenario normally. The methods before_hook and after_hook replicate the Before ('~#mass_benchmark') and After ('~#mass_benchmark') hooks (which actually just call the same method).
The variable $seconds_taken is set around the specific area for which I am timing. I am not timing the whole test there, just a critical portion of it; the remainder of the test is getting to that point, etc, which is not to be part of the timed portion, so I cannot just move the timing portion outside of this.
The issue may be with something I'm doing in those methods, but as far as I can tell, everything works normally (as indicated by well-placed puts statements). Any ideas are appreciated!
Currently Cucumber does not seem to support calling the block twice in an around hook. This can be demonstrated by the following feature file:
Feature: This scenario will print a line
Scenario: Print a line
When I print a line
And step definitions:
Around do |scenario, block|
Kernel.puts "START AROUND, status=#{scenario.status}"
block.call
Kernel.puts "BETWEEN CALLS, status=#{scenario.status}"
block.call
Kernel.puts "END AROUND, status=#{scenario.status}"
end
When /^I print a line$/ do
Kernel.puts "IN THE STEP DEFINITION"
end
When this is executed, Cucumber will print:
Scenario: Print line # features/test1.feature:3
START AROUND, status=skipped
IN THE STEP DEFINITION
When I print a line # features/test.rb:9
BETWEEN CALLS, status=passed
When I print a line # features/test.rb:9
END AROUND, status=passed
Evidently since the status of the scenario is already "passed", Cucumber does not re-execute it, though the output formatter receives the steps. I have not found any way to "reset" the status in the scenario API to get them to be re-run.
There are other problems with around hooks as well, for example you cannot set variables to the World in around hooks (like you can in before hooks). See also Cucumber issues 52 and 116 for more gory details.
One possibility might be to keep the passed-in block as it is, and call the ".call" method on a duplicate?
Something like (untested):
Around do |scenario, block|
30.times do
duplicate = block.dup
before_hook(scenario)
duplicate.call
after_hook(scenario)
end
end
Just make sure not to use ".clone" on the block, since clone will create an object with the same Id, resulting in every change made to the duplicate also affecting the original.

Resources