Test output to command line with RSpec - ruby

I want to do is run ruby sayhello.rb on the command line, then receive Hello from Rspec.
I've got that with this:
class Hello
def speak
puts 'Hello from RSpec'
end
end
hi = Hello.new #brings my object into existence
hi.speak
Now I want to write a test in rspec to check that the command line output is in fact "Hello from RSpec"
and not "I like Unix"
NOT WORKING. I currently have this in my sayhello_spec.rb file
require_relative 'sayhello.rb' #points to file so I can 'see' it
describe "sayhello.rb" do
it "should say 'Hello from Rspec' when ran" do
STDOUT.should_receive(:puts).with('Hello from RSpec')
end
end
Can someone point me in the right direction please?

Here's a pretty good way to do this. Copied from the hirb test_helper source:
def capture_stdout(&block)
original_stdout = $stdout
$stdout = fake = StringIO.new
begin
yield
ensure
$stdout = original_stdout
end
fake.string
end
Use like this:
output = capture_stdout { Hello.new.speak }
output.should == "Hello from RSpec\n"

The quietly command is probably what you want (cooked into ActiveSupport, see docs at api.rubyonrails.org). This snippet of RSpec code below shows how to ensure there is no output on stderr while simultaneously silencing stdout.
quietly do # silence everything
commands.each do |c|
content = capture(:stderr) { # capture anything sent to :stderr
MyGem::Cli.start(c)
}
expect(content).to be_empty, "#{c.inspect} had output on stderr: #{content}"
end
end

So you don't have to change your main ruby code I just found out you can do something like this:
def my_meth
print 'Entering my method'
p 5 * 50
puts 'Second inside message'
end
describe '#my_meth' do
it 'puts a 2nd message to the console' do
expect{my_meth}.to output(/Second inside message/).to_stdout
end
end
When checking for a desired output text I used it inside / / like a Regexp because after many many maaany tests and looking around, the STDOUT is everything that is outputted so I found it to be better to use Regex so you could check the whole STDOUT for the exact text that you want.
Like I put it, it works in the terminal just perfect.
//Just had to share this, it took me days to figure it out.

it "should say 'Hello from Rspec' when run" do
output = `ruby sayhello.rb`
output.should == 'Hello from RSpec'
end

Related

How can I appropriately mock out a method that returns yield?

It's fairly common in Ruby for methods that take blocks to look like this:
class File
def open(path, mode)
perform_some_setup
yield
ensure
do_some_teardown
end
end
It's also fairly idiomatic for a method to look like this:
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
I want to write a spec for this that doesn't hit the filesystem, which ensures it pulls the right word out of the file, something like:
describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
File.expects(:open).yields(StringIO.new(<<EOF))
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
The problem here is that, by mocking out the call to File.open, I've also removed its return value, which means that frobnicate will return nil. If I were to add something like File.returns('foo bar baz') to the chain, however, I'd end up with a test that doesn't actually hit any of the code I'm interested in; the contents of the block in frobnicate could do anything and the test would still pass.
How might I appropriately test my frobnicate method without hitting the filesystem? I'm not particularly attached to any particular testing framework, so if your answer is "use this awesome gem that'll do it for you" then I'm OK with that.
It seems like you just need to mock the call to File a little differently. I was getting syntax errors running your code as-is, so I'm not sure what version of RSpec you're on, but if you're on 3.x this will do the job:
frobnicate_spec.rb
gem 'rspec', '~> 3.4.0'
require 'rspec/autorun'
RSpec.configure do |config|
config.mock_with :rspec
end
def frobnicate
File.open('/path/to/something', 'r') do |f|
f.grep(/foo/).first
end
end
RSpec.describe 'frobnicate' do
it 'returns the first line containing the substring foo' do
allow(File).to receive(:open).and_call_original
allow(File).to receive(:open).and_yield StringIO.new <<-EOF
not this line
foo bar baz
not this line either
EOF
expect(frobnicate).to match(/foo bar baz/)
end
end
Invoke with ruby frobnicate_spec.rb so we can use a specified RSpec version.
Source: RSpec Mocks expecting messages and yielding responses
Using minitest it could be done like I post below. I have added the whole runnable file, so you can test it from the command line with ruby -Ilib:test test_file.rb:
def frobnicate
found_string = nil
File.open('/path/to/something', 'r') do |f|
found_string = f.grep(/foo/).first
end
found_string
end
class FrabnicateTest < Minitest::Test
def test_it_works
mock_file = StringIO.new(%(
not this line
foo bar baz
not hthis line either
))
search_result = nil
File.stub(:open, nil, mock_file) do
search_result = frobnicate
end
assert_match(/foo bar baz/, search_result)
end
end

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.

Ruby - Print with color output

I have a ruby script (Guardfile) that executes a rake command.
guard :shell do
watch(%r{^manifests\/.+\.pp$}) do |m|
spec = `rake spec`
retval = $?.to_i
case retval
when 0
if spec.length > 0 then
puts spec
n "#{m[0]} Tests Failed!", 'Rake Spec', :pending
else
puts spec
n "#{m[0]} Tests Passed!", 'Rake Spec', :pending
end
end
end
When I run a 'rake spec' from the command line, outputs are colorized.
How could I make it so the output of the ruby script is also colorized?
From command line:
From ruby script:
Update
I was able to sort-of work around the problem by using script
bash command preserve color when piping
spec = `script -q /dev/null rake spec`
This still has the downside of not scrolling the text in real time. While it does preserve the colors, it does not output anything until the very end.
Is there a more native way to do this that will allow for scrolling?
First, rake spec --color won't work, because you're passing --color to rake, and not rspec.
Jay Mitchell's suggestion for color should work - by putting this in your .rspec file:
--color
As for having "live" output, guard-shell has an eager command for this:
https://github.com/guard/guard-shell/blob/master/lib/guard/shell.rb#L37-L51
Unfortunately, guard-shell has 2 important shortcomings:
it doesn't give you access to the exit code
it doesn't properly report failures in Guard (which causes other tasks to run)
So the eager method of Guard::Shell is useless for our needs here.
Instead, the following should work:
# a version of Guard::Shell's 'eager()' which returns the result
class InPty
require 'pty'
def self.run(command)
PTY.spawn(command) do |r, w, pid|
begin
$stdout.puts
r.each {|line| $stdout.print line }
rescue Errno::EIO
end
Process.wait(pid)
end
$?.success?
rescue PTY::ChildExited
end
end
# A hack so that Guard::Shell properly throws :task_has_failed
class ProperGuardPluginFailure
def to_s
throw :task_has_failed
end
end
guard :shell, any_return: true do
watch(%r{^manifests\/.+\.pp$}) do |m|
ok = InPty.run('rake spec')
status, type = ok ? ['Passed', :success] : ['Failed', :failed]
n "#{m[0]} Tests #{status}!", 'Rake Spec', type
ok ? nil : ProperGuardPluginFailure.new
end
end
The above looks ideal for a new guard plugin - good idea?
I am unfamiliar with Guardfiles. Can you use gems? The colorize gem is great.
https://github.com/fazibear/colorize
Install it:
$ sudo gem install colorize
Use it:
require 'colorize'
puts "Tests failed!".red
puts "Tests passed!".green

How can I copy STDOUT to a file without stopping it showing onscreen using Ruby

I'm attempting to copy stdout to a file for logging purposes. I also want it to display in the Ruby console of the IDE I'm using.
I inserted this code into my script and it redirects $stdout to the my.log file:
$stdout.reopen("my.log", "w")
Does anyone know of a gem or technique to copy the contents of $stdout to a file and not redirect it to a file? Also, I am not using Rails just Ruby.
Something like this might help you:
class TeeIO < IO
def initialize orig, file
#orig = orig
#file = file
end
def write string
#file.write string
#orig.write string
end
end
Most of the methods in IO that do output ultimately use write, so you only have to override this one method. You can use it like this:
#setup
tee = TeeIO.new $stdout, File.new('out.txt', 'w')
$stdout = tee
# Now lots of example uses:
puts "Hello"
$stdout.puts "Extending IO allows us to expicitly use $stdout"
print "heres", :an, :example, "using", 'print', "\n"
48.upto(57) do |i|
putc i
end
putc 10 #newline
printf "%s works as well - %d\n", "printf", 42
$stdout.write "Goodbye\n"
After this example, the following is written identically to both the console and to the file:
Hello
Extending IO allows us to expicitly use $stdout
heresanexampleusingprint
0123456789
printf works as well - 42
Goodbye
I won't claim this technique is fail proof, but it should work for simple uses of stdout. Test it for your use.
Note that you don't have to use reopen on $stdout unless you want to redirect output from a child process or an uncooperative extension. Simply assigning a different IO object to it will work for most uses.
RSpec
The RSpec command line takes a reference to $stdout before you can get any code to run to reassign it, so this doesn't work. reopen still works in this case as you're changing the actual object pointed to by both $stdout and the reference that RSpec has, but this doesn't give you output to both.
One solution is to monkey-patch $stdout like this:
$out_file = File.new('out.txt', 'w')
def $stdout.write string
$out_file.write string
super
end
This works, but as with all monkey patching, be careful. It would be safer to use your OS's tee command.
If you are using Linux or Mac OS, the tee command available in the OS makes it easy to do this. From its man page:
NAME
tee -- pipe fitting
SYNOPSIS
tee [-ai] [file ...]
DESCRIPTION
The tee utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered.
So something like:
echo '>foo bar' | tee tmp.out
>foo bar
echos the output to STDOUT and to the file. Catting the file gives me:
cat tmp.out
>foo bar
Otherwise, if you want to do it inside your code, it's a simple task:
def tee_output(logfile)
log_output = File.open(logfile, 'w+')
->(o) {
log_output.puts o
puts o
}
end
tee = tee_output('tmp.out')
tee.call('foo bar')
Running it:
>ruby test.rb
foo bar
And checking the output file:
>cat tmp.out
foo bar
I'd use "w+" for my file access to append to the output file, rather than over-write it.
CAVEAT: This opens the file and leaves it open during the life of the code after you've called the tee_output method. That bothers some people, but, personally, it doesn't bother me because Ruby will close the file when the script exits. In general we want to close files as soon as we're done with them, but in your code, it makes more sense to open it and leave it open, than to repeatedly open and close the output file, but your mileage might vary.
EDIT:
For Ruby 1.8.7, use lambda instead of the new -> syntax:
def tee_output(logfile)
log_output = File.open(logfile, 'w+')
lambda { |o|
log_output.puts o
puts o
}
end
tee = tee_output('tmp.out')
tee.call('foo bar')
I know it's a old question, but I found myself in the same situation. I wrote a Multi-IO Class which extends File and overrode write puts and close methods, I also made sure its thread safe:
require 'singleton'
class MultiIO < File
include Singleton
##targets = []
##mutex = Mutex.new
def self.instance
self.open('/dev/null','w+')
end
def puts(str)
write "#{str}\n"
end
def write(str)
##mutex.synchronize do
##targets.each { |t| t.write str; flush }
end
end
def setTargets(targets)
raise 'setTargets is a one-off operation' unless ##targets.length < 1
targets.each do |t|
##targets.push STDOUT.clone if t == STDOUT
##targets.push STDERR.clone if t == STDERR
break if t == STDOUT or t == STDERR
##targets.push(File.open(t,'w+'))
end
self
end
def close
##targets.each {|t| f.close}
end
end
STDOUT.reopen MultiIO.instance.setTargets(['/tmp/1.log',STDOUT,STDERR])
STDERR.reopen STDOUT
threads = []
5.times.each do |i|
threads.push(
Thread.new do
10000.times.each do |j|
STDOUT.puts "out#{i}:#{j}"
end
end
)
end
5.times.each do |i|
threads.push(
Thread.new do
10000.times.each do |j|
STDERR.puts "err#{i}:#{j}"
end
end
)
end
threads.each {|t| t.join}
Tiny improvement from matts answer:
class TeeIO < IO
def initialize(ios)
#ios = ios
end
def write(string)
#ios.each { |io| io.write string }
end
end

RSpec: how do I write a test that expects certain output but doesn't care about the method?

I'm trying to get my head around test-driven design, specifically RSpec. But I'm having trouble with some of the examples from The RSpec Book.
In the book, we test for output on $STDOUT like this:
output = double('output')
game = Game.new
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start()
Well, that works after a fashion. But why on earth should I care if the Game object uses the puts() method? If I change it to print(), should it really break the test? And, more importantly, isn't this against the one of the principals of TDD - that I should be testing what the method does (the design) rather than how it does it (the implementation)?
Is there some way I can write a test that just tests what ends up on $STDOUT, without looking at what method puts it there?
Create a display class with the ability to write the status out.
You production code will make use of this display object so you are free to change how you write to STDOUT. There will be just one place for this logic while your tests rely on the abstraction.
For example:
output = stub('output')
game = Game.new(output)
output.should_receive(:display).with('Welcome to Codebreaker!')
game.start()
While your production code will have something such as
class Output
def display(message)
# puts or whatever internally used here. You only need to change this here.
end
end
I'd make this test pass by doing the following:
def start
#output.display('Welcome to Codebreaker!')
end
Here the production code doesn't care how the output is displayed. It could be any form of display now the abstraction is in place.
All of the above theory is language agnostic, and works a treat. You still mock out things you don't own such as third party code, but you are still testing you are performing the job at hand via your abstraction.
take a look at this post. Nick raised questions about the same example, and a very interesting conversation follows in the comments. Hope you find it helpful.
Capture $stdout and test against that instead of trying to mock the various methods that might output to stdout. After all, you want to test stdout and not some convoluted method for mimicking it.
expect { some_code }.to match_stdout( 'some string' )
Which uses a custom Matcher (rspec 2)
RSpec::Matchers.define :match_stdout do |check|
#capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
#capture = $stdout
$stdout = stdout_saved
end
#capture.string.match check
end
failure_message_for_should do
"expected to #{description}"
end
failure_message_for_should_not do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{#capture.string}]"
end
end
RSpec 3 has changed the Matcher API slightly.
failure_message_for_should is now failure_message
failure_message_for_should_not is now failure_message_when_negated
supports_block_expectations? has been added to make errors clearer for blocks.
See Charles' answer for the complete rspec3 solution.
The way I'd test it is with a StringIO object. It acts like a file, but doesn't touch the filesystem. Apologies for the Test::Unit syntax - feel free to edit to RSpec syntax.
require "stringio"
output_file = StringIO.new
game = Game.new(output_file)
game.start
output_text = output_file.string
expected_text = "Welcome to Codebreaker!"
failure_message = "Doesn't include welcome message"
assert output_text.include?(expected_text), failure_message
I came across this blog post which helped me solve this issue:
Mocking standard output in rspec.
He sets up before/after blocks, and I ended up doing them inside the actual rspec itself, for some reason I couldn't get it to work from my spec_helper.rb as recommended.
Hope it helps!
An updated version of Matt's answer for RSpec 3.0:
RSpec::Matchers.define :match_stdout do |check|
#capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
#capture = $stdout
$stdout = stdout_saved
end
#capture.string.match check
end
failure_message do
"expected to #{description}"
end
failure_message_when_negated do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{#capture.string}]"
end
def supports_block_expectations?
true
end
end

Resources