RSpec: how to test Rails logger message expectations?

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:

class BaseWorker

  def execute
    logger.info 'Starting the worker...'
  end

end

And a spec like:

describe BaseWorker do

  it 'should log an info message' do
    base_worker = BaseWorker.new
    logger_mock = double('Logging::Rails').as_null_object
    Logging::Rails.stub_chain(:logger, :info).and_return(logger_mock)

    logger_mock.should_receive(:info).with('Starting the worker...')
    base_worker.execute
    Logging::Rails.unstub(:logger)
  end

end

I get the following failure message:

 Failure/Error: logger_mock.should_receive(:info).with('Starting worker...')
   (Double "Logging::Rails").info("Starting worker...")
       expected: 1 time
       received: 0 times

I've tried out several different approaches to get the spec to pass. This works for example:

class BaseWorker

  attr_accessor :log

  def initialize
    @log = logger
  end

  def execute
    @log.info 'Starting the worker...'
  end

end

describe BaseWorker do
  it 'should log an info message' do
    base_worker = BaseWorker.new
    logger_mock = double('logger')
    base_worker.log = logger_mock

    logger_mock.should_receive(:info).with('Starting the worker...')
    base_worker.execute
  end
end

But having to setup an accessible instance variable like that seems like the tail is wagging the dog here. (Actually, I'm not even sure why copying logger to @log would make it pass.)

What's a good solution for testing the logging?

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

How to test Rails logger message expectation in Rails, Note: In controller and model specs, you have to put this line before the message is logged. Special Thanks. logging - RSpec: how to test  Aside from the DSL that Rails provide for the model, RSpec also provides us with model specs, which allow us to properly unit test our models. We saw how we can tackle issues in the BDD way, by using specs and test-driven development.

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

RSpec: how to test Rails logger message expectations?, 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: class  I'm a huge fan for RSpec for testing Rails applications, and I've found that although the rspec-rails gem is a great starting point, there is a general lack of best practices for how to properly unit test controllers.

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.

Expect a message - Message expectations - RSpec Mocks, require "account" describe Account do context "when closed" do it "logs an account closed message" do logger = double("logger") account = Account.new  matches?(actual) failure_message These methods are also part of the matcher protocol, but are optional: does_not_match?(actual) failure_message_when_negated description supports_block_expectations? RSpec ships with a number of built-in matchers and a DSL for writing custom matchers. Issues. The documentation for rspec-expectations is a work in

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

logging, logging - RSpec: how to test Rails logger message expectations? - Stac Use Instruments (Product menu -> Profile) and test for Leaks. If leaks are detected, get rid of them, see if you get more memory warnings. If yes, test Allocations, see what's using memory the most and try to optimize memory usage. RSpec::Benchmark. Performance testing matchers for RSpec to set expectations on speed, resources usage and scalability. RSpec::Benchmark is powered by: benchmark-perf for measuring execution time and iterations per second. benchmark-trend for estimating computation complexity. benchmark-malloc for measuring object and memory allocations. Why?

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

Can't assert Rails.logger call when migrating from Rails 4 to Rails 5 , Rails 5.0.0.1 rspec-rails 3.5.2 rspec-mocks 3.5.0 Ruby 2.3.1 I am currently migrating our test I am currently migrating our test suite to Rails 5 and am almost done except for two more failing specs. It logs correctly when I comment out the expect() line. Allow Rails.logger to receive system info messages Built in matchers. rspec-expectations ships with a number of built-in matchers. Each matcher can be used with expect(..).to or expect(..).not_to to define positive and negative expectations respectively on an object. Most matchers can also be accessed using the ().should and ().should_not syntax; see using should syntax for why we

Custom RSpec Matcher For Partial JSON, How to write a custom RSpec JSON matcher for a string that is partially of the data, and the logging of the message all independently from one another. However, for various reasons we made the trade off to not test each part individually. it 'logs the correct experiment name' do expect(Rails.logger).to  This of course is for Rails 3 and up. If you have anything prior to Rails 3 then add this instead: def logger RAILS_DEFAULT_LOGGER end Once you have your logging statements in place you can enter . tail -f log/test.log in your terminal shell in order to watch your logging statements while the tests are run. Of course in your actual rspec test you would enter something such as. logger.debug "#{1.class}" # => Fixnum

Rails Logger and Rails Logging Best Practices, I, [2019-01-04 05:14:33 #123] INFO -- Main: Check out this info! As for the message format that gets printed to the log, we have some additional  If you want to use rspec-expectations with another tool, like Test::Unit, Minitest, or Cucumber, you can install it directly: gem install rspec-expectations Contributing. Once you've set up the environment, you'll need to cd into the working directory of whichever repo you want to work in.

Advice regarding rspec $logger.info : ruby, Hi, I am very new to Ruby. I came across a task to test a ruby file but I would like to test a terminal output . How am I going to use rspec to test $logger.indo? Thank you Hmm. Which means I can't do like expect(logger).to eq "hello" ? level 1. It thinks your message is a second argument to the be method, instead of to the to method.. Wrap true in parentheses and it should work, or just use be_true as the other answer suggests.

Comments
  • That question did arise several times on SO, see for example here and here and the general consensus was that you don't test logging unless it's a project requirement.
  • Art, thanks for the comment. I did read those ones. That may be the ultimate answer.
  • I have the similar case expect my expected string is a partial string, I could not figure out so far, how to deal with it, any help?
  • @AmolPujari expect(Rails.logger).to receive(:info).with(/partial_string/) where "partial_string" is the string you're looking for. Simple Regex compare
  • This is great, I'm checking I don't get anything at all logged to error and checking against Rspec's anything matcher does this nicely: expect(Rails.logger).to_not receive(:error).with(anything)
  • what exactly do you mean by "you have to put this line before the message is logged"? The expectation appears in the code before the code that generates the log? I'm doing that and getting an error because the logger is getting the message that is logged from stuff being done in my let expressions before the it block even runs
  • @sixty4bit it means, that expext.. receive works as an event listener - you have to set it up first and then launch the code that will log the message you want to catch