Suppress console output during RSpec tests

RubyRspec

Ruby Problem Overview


I am testing the class which put on the console some messages (with puts, p warnings and etc.). I am just wondering if there is any ability to suppress this output during RSpec tests ?

Ruby Solutions


Solution 1 - Ruby

I suppress puts output in my classes by redirecting $stout to a text file. That way, if I need to see the output for any reason, it is there but it doesn't muddy up my test results.

#spec_helper.rb
RSpec.configure do |config|
  config.before(:all, &:silence_output)
  config.after(:all,  &:enable_output)
end

public
# Redirects stderr and stout to /dev/null.txt
def silence_output
  # Store the original stderr and stdout in order to restore them later
  @original_stderr = $stderr
  @original_stdout = $stdout
  
  # Redirect stderr and stdout
  $stderr = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
  $stdout = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
end

# Replace stderr and stdout so anything else is output correctly
def enable_output
  $stderr = @original_stderr
  $stdout = @original_stdout
  @original_stderr = nil
  @original_stdout = nil
end

EDIT:

In response to the comment by @MyronMarston, it probably would be smarter to just insert the methods directly into before and after as blocks.

#spec_helper.rb
RSpec.configure do |config|
  original_stderr = $stderr
  original_stdout = $stdout
  config.before(:all) do 
    # Redirect stderr and stdout
    $stderr = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
    $stdout = File.new(File.join(File.dirname(__FILE__), 'dev', 'null.txt'), 'w')
  end
  config.after(:all) do 
    $stderr = original_stderr
    $stdout = original_stdout
  end
end

It looks a little cleaner and keeps methods off of main. Also, note that if you are using Ruby 2.0, you can use __dir__ instead of File.dirname(__FILE__).

EDIT2

Also it should be mentioned, that you can forward to true os /dev/null by using File::NULL as it was introduced in Ruby v 1.9.3. (jruby 1.7)

Then the code snippet will look as following:

#spec_helper.rb
RSpec.configure do |config|
  original_stderr = $stderr
  original_stdout = $stdout
  config.before(:all) do
    # Redirect stderr and stdout
    $stderr = File.open(File::NULL, "w")
    $stdout = File.open(File::NULL, "w")
  end
  config.after(:all) do
    $stderr = original_stderr
    $stdout = original_stdout
  end
end

Solution 2 - Ruby

Try stubbing methods that make the output in a before block, e.g.

before do
  IO.any_instance.stub(:puts) # globally
  YourClass.any_instance.stub(:puts) # or for just one class
end

This is explicit, so you won't miss anything you don't want to miss. If you don't care about any output and the method above doesn't work you can always stub the IO object itself:

before do
  $stdout.stub(:write) # and/or $stderr if needed
end

Solution 3 - Ruby

An Rspec3.0 Version would be => in spec_helper.rb

RSpec.configure do |c|
  c.before { allow($stdout).to receive(:puts) }
end

it will act as before(:each)

but :each is default, so no need to write it explicitly

Solution 4 - Ruby

Tested with rspec-core (~> 3.4.0)

In describe block you could do

# spec_helper.rb
def suppress_log_output
  allow(STDOUT).to receive(:puts) # this disables puts
  logger = double('Logger').as_null_object
  allow(Logger).to receive(:new).and_return(logger)
end

# some_class_spec.rb
RSpec.describe SomeClass do
  before do
    suppress_log_output
  end
end

This way you have the advantage of toggling log output for specific tests. Note, this will not suppress rspec warnings, or messages from rspec.

Another way to disable warnings coming from gems:

add config.warnings = false to spec_helper

If you wanted to suppress only certain logger methods, like error, info, or warn you could do

allow_any_instance_of(Logger).to receive(:warn).and_return(nil)

To disable warnings coming from the rspec gem

allow(RSpec::Support).to receive(:warning_notifier).and_return(nil)

but this is generally discouraged because it is meant as a way to let you know you are doing something smelly in your tests.

Solution 5 - Ruby

If you want to suppress output for a single test, there is a more concise way:

> it "should do something with printing" do > silence_stream(STDOUT) do > foo.print.should be_true > end > end

You may want to change STDOUT to STDERR if your test prints an error.

Solution 6 - Ruby

Updated answer for Rails 5, in a one-off situation:

before do
  RSpec::Mocks.with_temporary_scope do
    allow(STDOUT).to receive(:puts)
  end
end

You can make this into a method in spec_helper if you'll be doing this a lot.

Solution 7 - Ruby

After trying all of these examples, I ended up using this varation which does not silence or mute binding.pry

# frozen_string_literal: true

RSpec.configure do |config|
  config.before(:each) do
    allow($stdout).to receive(:puts)
    allow($stdout).to receive(:write)
  end
end

Solution 8 - Ruby

It can be useful to inject an IO object defaulting to STDOUT. This also makes it easier to assert on the output if you want to.

E.g.

def my_method(arg, io: STDOUT)
  io.puts "hello"
  arg.reverse
end

And then in your test:

# Suppress it.

my_method("hi", io: StringIO.new)

# Assert on it.

io = StringIO.new
my_method("hi", io: io)

output = io.tap(&:rewind).read
expect(output).to include("hello")

Solution 9 - Ruby

You could have the object itself supress based on the environment:

class Foo
  def call
    puts("blah blah")
    # ...
  end

  private
  
  def puts(msg)
    return if ENV['APP_ENV'] == 'test'
    super
  end
end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncethView Question on Stackoverflow
Solution 1 - RubyCharles CaldwellView Answer on Stackoverflow
Solution 2 - RubyMichał KwiatkowskiView Answer on Stackoverflow
Solution 3 - RubyjaiksView Answer on Stackoverflow
Solution 4 - Rubylfender6445View Answer on Stackoverflow
Solution 5 - Rubyshock_oneView Answer on Stackoverflow
Solution 6 - Rubyconcept47View Answer on Stackoverflow
Solution 7 - RubyChris HoughView Answer on Stackoverflow
Solution 8 - RubyHenrik NView Answer on Stackoverflow
Solution 9 - RubyKrisView Answer on Stackoverflow