RSpec: how to test Rails logger message expectations?

后端 未结 6 968
囚心锁ツ
囚心锁ツ 2020-12-24 00:17

I am trying to test that the Rails logger receives messages in some of my specs. I am using the Logging gem.

Let\'s say that I have a class like this:



        
相关标签:
6条回答
  • 2020-12-24 00:48

    While I agree you generally don't want to test loggers, there are times it may be useful.

    I have had success with expectations on Rails.logger.

    Using RSpec's deprecated should syntax:

    Rails.logger.should_receive(:info).with("some message")
    

    Using RSpec's newer expect syntax:

    expect(Rails.logger).to receive(:info).with("some message")
    

    Note: In controller and model specs, you have to put this line before the message is logged. If you put it after, you'll get an error message like this:

    Failure/Error: expect(Rails.logger).to receive(:info).with("some message")
           (#<ActiveSupport::Logger:0x007f27f72136c8>).info("some message")
               expected: 1 time with arguments: ("some message")
               received: 0 times
    
    0 讨论(0)
  • 2020-12-24 01:02

    If your goal is to test logging functionality you may also consider verifying the output to standard streams.

    This will spare you the mocking process and test whether messages will actually end up where they supposed to (STDOUT/STDERR).

    With RSpec's output matcher (introduced in 3.0) you can do the following:

    expect { my_method }.to output("my message").to_stdout
    expect { my_method }.to output("my error").to_stderr
    

    In case of libraries such as Logger or Logging you may have to use output.to_<>_from_any_process.

    0 讨论(0)
  • 2020-12-24 01:04

    With RSpec 3+ version

    Actual code containing single invocation of Rails.logger.error:

    Rails.logger.error "Some useful error message"
    

    Spec code:

    expect(Rails.logger).to receive(:error).with(/error message/)
    

    If you want the error message to be actually logged while the spec runs then use following code:

    expect(Rails.logger).to receive(:error).with(/error message/).and_call_original
    

    Actual code containing multiple invocations of Rails.logger.error:

    Rails.logger.error "Technical Error Message"
    Rails.logger.error "User-friendly Error Message"
    

    Spec code:

    expect(Rails.logger).to receive(:error).ordered
    expect(Rails.logger).to receive(:error).with(/User-friendly Error /).ordered.and_call_original
    

    Also if you care about just matching the first message and not any subsequent messages then you can use following

      expect(Rails.logger).to receive(:debug).with("Technical Error Message").ordered.and_call_original
      expect(Rails.logger).to receive(:debug).at_least(:once).with(instance_of(String)).ordered
    

    Note in above variation setting .ordered is important else expectations set start failing.

    References:

    http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/setting-constraints/matching-arguments

    http://www.relishapp.com/rspec/rspec-mocks/v/3-4/docs/setting-constraints/message-order

    0 讨论(0)
  • 2020-12-24 01:05

    Instead of using this line before the message is logged:

    expect(Rails.logger).to receive(:info).with("some message")
    something that triggers the logger...
    

    You could set the Rails logger as a spy and use have_received instead:

    allow(Rails.logger).to receive(:info).at_least(:once)
    
    something that triggers the logger...
    
    expect(Rails.logger).to have_received(:info).with("some message").once
    
    0 讨论(0)
  • 2020-12-24 01:09

    Even I had very similar error:

    Failure/Error: expect(Rails.logger).to receive(:info).with("some message")
           (#<ActiveSupport::Logger:0x007f27f72136c8>).info("some message")
               expected: 1 time with arguments: ("some message")
               received: 0 times
    

    The below worked for me,

    expect { my_method }.
                to output(/error messsage/).to_stdout_from_any_process
    

    Reference: https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/output-matcher

    0 讨论(0)
  • 2020-12-24 01:15

    If you want to keep consistency in your tests, but setting the expectations at last you need to add in your setup:

    setup do
     allow(Rails.logger).to receive(:info)
    end
    ...
    
    it 'should log an info message' do
     {code}
    
      expect(Rails.logger).to have_received(:info).with('Starting the worker...')
    end
    
    
    0 讨论(0)
提交回复
热议问题