suppresing output to console with ruby
Asked Answered
H

6

22

I am writing some unit tests like the following:

def executing_a_signal
  a_method(a_signal.new, a_model, a_helper);
  assert_equal(new_state, a_model.state)
end

The tests work fine, but the method which runs just before the assertion to execute the logic prints various messages to the console, mainly via puts.

Is there a quick, perhaps built-in, way to suppress that output to the console? I am only interested in the final effect of the method on the model object, and for the sake of keeping the console clean basically, I was hoping to find a way to simply prevent all output to the console without re-writing or commenting out those puts statements just for my tests.

It is definitely not a critical issue, but would very much like to hear any thoughts or ideas (or workaround) on it.

Hopefully answered 30/9, 2009 at 3:42 Comment(0)
I
34

I use the following snippet in tests to capture and test STDOUT

def capture_stdout(&block)
  original_stdout = $stdout
  $stdout = fake = StringIO.new
  begin
    yield
  ensure
    $stdout = original_stdout
  end
  fake.string
end

With this method, the above would become:

def executing_a_signal
  capture_stdout { a_method(a_signal.new, a_model, a_helper) }
  assert_equal(new_state, a_model.state)
end
Interaction answered 30/9, 2009 at 3:52 Comment(4)
Thanks, this probably means that there is no standard built-in way?Hopefully
I maintain build scripts in Ruby/Rake and this trick even disabled the console output of devenv (Visual Studio) when called using the Ruby sh function. Thanks a lot!Pedology
If you use the STDOUT constant anywhere in your code you're testing, you will have to redefine that separately.Unquiet
This is awesome. For some reason a gem we are using is constantly generating output(someone might have left a puts in there), and this is an easy way to take care of that without monkey patching the gem. Thanks!Ferriferous
N
11

A slightly cleaner take on @cldwalker's solution:

def silenced
  $stdout = StringIO.new

  yield
ensure
  $stdout = STDOUT
end

silenced do
  something_that_prints
end
Nonstriated answered 14/9, 2016 at 8:33 Comment(0)
M
4

There's two solutions: redirecting where puts writes to (the solution given by @cldwalker above), or overwriting the puts method itself to be a no-op. (The implementation should be obvious: module Kernel; def puts(*args) end end).

However, in this case, what would really be the best solution is "listening to your tests". Because, oftentimes when something is awkward to test, your tests are really trying to tell you that something is wrong with your design. In this case, I smell a violation of the Single Responsibility Principle: why the heck does a Model object need to know how to write to the console? Its responsibility is representing a Domain Concept, not logging! That's what Logger objects are for!

So, an alternative solution would be to have the Model object delegate the responsibility for logging to a Logger object, and use dependency injection to inject the Model object with a suitable Logger object. That way, you can simply inject a fake logger for the test. Here's an example:

#!/usr/bin/env ruby

class SomeModel
  def initialize(logger=Kernel) @logger = logger end
  def some_method_that_logs; @logger.puts 'bla' end
end

require 'test/unit'
require 'stringio'
class TestQuietLogging < Test::Unit::TestCase
  def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end
  def teardown; $> = @old_stdout end

  def test_that_default_logging_is_still_noisy
    SomeModel.new.some_method_that_logs

    assert_equal "bla\n", @fake_logdest.string
  end

  def test_that_logging_can_be_made_quiet
    fake_logger = Object.new
    def fake_logger.puts *args; end

    SomeModel.new(fake_logger).some_method_that_logs

    assert_equal '', @fake_logdest.string
  end
end

At the very least, the Model object should take the IO object that it is logging to as an argument, so that you can simply inject StringIO.new into it for the test:

#!/usr/bin/env ruby

class SomeModel
  def initialize(logdest=$>) @logdest = logdest end
  def some_method_that_logs; @logdest.puts 'bla' end
end

require 'test/unit'
require 'stringio'
class TestQuietLogging < Test::Unit::TestCase
  def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end
  def teardown; $> = @old_stdout end

  def test_that_default_logging_is_still_noisy
    SomeModel.new.some_method_that_logs

    assert_equal "bla\n", @fake_logdest.string
  end

  def test_that_logging_can_be_made_quiet
    fake_logdest = (@fake_logdest = StringIO.new)

    SomeModel.new(fake_logdest).some_method_that_logs

    assert_equal '', @fake_logdest.string
    assert_equal "bla\n", fake_logdest.string
  end
end

If you still want to be able to just say puts whatever in your Model or you are afraid that someone might forget to call puts on the logger object, you can provide your own (private) puts method:

class SomeModel
  def initialize(logdest=$>) @logdest = logdest end
  def some_method_that_logs; puts 'bla' end
  private
  def puts(*args) @logdest.puts *args end
end
Mandatory answered 30/9, 2009 at 16:10 Comment(0)
G
1

reopen '/dev/null'

Another option is to redirecting to /dev/null with:

STDOUT.reopen('/dev/null', 'w')
STDERR.reopen('/dev/null', 'w')

This technique is used on WEBrick::Daemon of the stdlib (toggle source).

It has the advantage of being more efficient than StringIO.new since it does not store the stdout on a StringIO, but it is less portable.

Glasswort answered 10/10, 2014 at 18:42 Comment(0)
R
0

I just used the below code at the beginning of my .rb file.. so it disable all console print statements..

 def puts(*args) end
Rockyrococo answered 2/8, 2017 at 10:13 Comment(0)
B
0

March 2024 update:

Use Minitest capture_io:

capture_io do
  a_method(a_signal.new, a_model, a_helper)
end

Its implementation is almost the same as "cldwalker" suggested.

Bohon answered 21/3 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.