Sideband error reporting to rspec

24 views
Skip to first unread message

Tim Mertens

unread,
Nov 25, 2015, 11:31:06 AM11/25/15
to rspec
Happy Thanksgiving!

I am testing an application that reports all errors through a common interface, which then distributes errors to various loggers (rails logs, logentries, etc).  Each reporter logs messages based on severity and environment.  In our test environment, the generic logging interface is set to always re-raise reported errors such that tests will always fail if an error is reported through this interface that is not expected.  In the majority of cases, this means that conditions where such errors would normally be reported, handled and swallowed in production will instead re-raise the exception (in test) and ultimately cause the test to fail if the error is not explicitly expected.

However, we still observe conditions where:
  1. class A calls class B calls class C.
  2. class C raises an exception.
  3. class B catches said exception and logs it via the Logger.
  4. The Logger re-raises the error.
  5. class A catches the re-raised error but swallows it completely.
  6. The test passes if there are no failing expectations as a result, even though an exception occurred.
Is there some way to report errors to rspec via a sideband messaging channel, such that we can implement a logger that would simply report errors directly to rspec and bypass the need for an exception to bubble all the way up to the top of the stack in order for a test to fail?

The only possible solution I know of so far would be to set a global expectation for the logging class not to receive an error message, and override that behavior in individual tests; however, this feels like the wrong solution and I'm not sure it would actually work as desired to modify the expectation behavior from `expect().not_to receive` (in a global before hook) to `allow().to receive`, or `expect().to receive`.  Or we would have to override the default expectation via metadata in individual tests which again is not an ideal solution.

Here is a rudimentary example of the problem:

class RSpecLogger
  def error(e)
    # somehow report sideband error to rspec
  end
end

class SomeOtherReporter
  def error(e)
    puts e.message
  end
end

class MyErrorLogger
  LOGGERS = [
    SomeOtherReporter.new,
    RSpecLogger.new
  ]

  def self.error(exception)
    LOGGERS.each { |r| r.error(exception) }

    raise exception if should_reraise? # This could be replaced entirely via sideband reporting
  end

  def self.should_reraise?
    true # if in test environment
  end
end

describe MyErrorLogger do
  it 'should fail even if reraise is caught elsewhere' do
    begin
      begin
        # Something raises an exception
        raise StandardError.new("Foo message")
      rescue => e
        # We rescue and log it
        described_class.error(e)
      end
    rescue => e
      # the reraised error is swallowed somewhere further up the stack, so the test passes if no assertions fail.
      nil
    end
  end
end

Thanks,
Tim

Myron Marston

unread,
Nov 25, 2015, 12:34:02 PM11/25/15
to rs...@googlegroups.com

Try this:

class RSpecLogger
  def errors
    @errors ||= []
  end

  def error(e)
    errors << e
  end
end

RSpec.configure do |c|
  c.before do
    @rspec_logger = RSpecLogger.new
  end

  c.after do
    expect(@rspec_logger.errors).to be_empty,
      "Got some logged errors: #{@rspec_logger.errors.map(&:message).join("\n")}"
  end
end

--
You received this message because you are subscribed to the Google Groups "rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rspec+un...@googlegroups.com.
To post to this group, send email to rs...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/d0fcb0da-8578-49f0-be1e-9a9094276fe0%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Tim Mertens

unread,
Dec 3, 2015, 9:54:40 AM12/3/15
to rspec
Thanks Myron!  I haven't had a chance yet to try it out but this looks like it should do the trick.
Reply all
Reply to author
Forward
0 new messages