0

I have the following Ruby class I want to test:

class Logger
  # ...

  def process
    X.process
  rescue StandardError => e
    ::Rails.logger.error('Progname') { 'Could not produce the audit log' }
    ::Rails.logger.error('Progname') { e.message }
    ::Rails.logger.error('Progname') { e.backtrace.join("\n") }
  end
end

And my RSpec test that currently works:

require 'spec_helper'

describe Logger do
  before do
    stub_const('Rails', double('Rails', logger: rails_logger))
  end

  let(:rails_logger) { instance_double(Logger, error: nil) }

  describe '#process' do
    subject(:process) { log.process }

    context 'when an exception occurs' do
      let(:error) do
        StandardError.new('Something went wrong')
      end

      before do
        allow(X).to receive(:process).and_raise(error)
        allow(error).to receive(:backtrace)
          .and_return(['backtrace:1', 'backtrace:2'])
      end

      it 'logs an error message' do
        expect(rails_logger).to(receive(:error).with('Progname')) do |&block|
          expect(block.call).to eq('Could not produce the audit log')
        end

        expect(rails_logger).to(receive(:error).with('Progname')) do |&block|
          expect(block.call).to eq('Something went wrong')
        end

        expect(rails_logger).to(receive(:error).with('Progname')) do |&block|
          expect(block.call).to eq("backtrace:1\nbacktrace:2")
        end

        process
      end
    end
  end
end

Now I want to use the have_received form instead so I changed my code to:

it 'logs an error message' do
  process

  expect(rails_logger).to(have_received(:error).with('Progname')) do |&block|
    expect(block.call).to eq('Could not produce the audit log')
  end

  expect(rails_logger).to(have_received(:error).with('Progname')) do |&block|
    expect(block.call).to eq('Something went wrong')
  end

  expect(rails_logger).to(have_received(:error).with('Progname')) do |&block|
    expect(block.call).to eq("backtrace:1\nbacktrace:2")
  end        
end

but the block evaluation fails:

Failure/Error: expect(block.call).to eq('Could not produce the audit log')
     
       expected: "Could not produce the audit log"
            got: "Something went wrong"

It seems the strategy of the block evaluation is different there and if the expect within the block fails, instead of trying another expectation it fails immediately. I don't know how to rewrite my existing code.

Thanks.

1
  • Rescuing StandardError in your logger isn't a very good idea as it will just make debugging more difficult. There are better alternatives for logging errors. Commented Feb 10 at 10:57

1 Answer 1

0

To simplify, instead of stubbing the logger with an instance, use allow:

before do
  allow(Rails.logger).to receive(:error)
end

Then your example becomes and passes:

it 'logs an error message' do
  yielded = []

  process

  expect(Rails.logger).to have_received(:error).with('Progname').thrice do |&block|
    yielded << block.call
  end
  expect(yielded).to eq ["Could not produce the audit log", "Something went wrong", "backtrace:1\nbacktrace:2"]
end
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @mochatiger, it was indeed a solution I'd been considering but that's a little "out of the box" with a manual instrumentation. I was hoping a more native solution without extra work. Maybe I could create an issue on rspec, because I don't know why there is no have_received solution here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.