Testing Logger in Ruby

I came across an interesting dilemma while working on a recent project. I had a logger object within a class which writes error messages to an external log file.

While I normally don’t test log files, there was a long formatted message produced by the logger which warrants some test coverage since it is triggered when certain error conditions occur, which means it is also dependent on the kind of StandardErrors that occur.

My first approach was to stub out the ‘error’ method on the logger object to return a mock response using mocha with MiniTest and asserting against the message when certain errors are raised.

However this apporach does not feel I’m testing the SUT but rather defining a canned response which will have to change if the underlying system implementation changes in the future.

I decided to abandon the mock/stub approach. I created a new class object called TestLogger which responds to the ‘error’ method call and is simply as follows:

 1 class FakeLogger
 2   attr_accessor :errors
 3 
 4   def initialize
 5     @errors = []
 6   end
 7 
 8   def error(exception)
 9     @errors << exception
10   end
11 end

Within the class which has the logging, I also refactor for it to accept an optional Logger as a parameter:

 1 class ExceptionalClass
 2   # rest of class implementation
 3 
 4   def initialize(logger: Logger.new("mylogs.log"))
 5     @logger = logger
 6   end
 7 
 8   def raise_an_error!
 9     raise StandardError, "An error has occured"
10   rescue => e
11     logger.error "An exception was raised: #{e.message} #{e.class}"
12   end
13 end

Within my unit tests, I just instantiate the FakeLogger class and pass it as a params into the logger method:

 1 test "it should log error messsages" do
 2     fake_logger = FakeLogger.new
 3     exceptional_class = ExceptionalClass.new(logger: fake_logger)
 4 
 5     assert_raise StandardError do
 6       exceptional_class.raise_an_error!
 7     end
 8 
 9     assert_includes fake_logger.errors, "An exception was raised: An error has occured StandardError"
10   end

I was able to create assertions and test it against the fake logger through its @errors array in the test environment. In the production environment I am also able to pass in a custom logger of my choice or leave it as the default. I feel that this approach is more flexible to change and also provide adequate test coverage on rescuing the exceptions being raised.

Happy Hacking!