RSpec: как тестировать ожидания сообщений журнала Rails?
Я пытаюсь проверить, что регистратор Rails получает сообщения в некоторых моих спецификациях. Я использую журнал ведения журнала.
Скажем, что у меня такой класс:
class BaseWorker
def execute
logger.info 'Starting the worker...'
end
end
И такая спецификация, как:
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
Я получаю следующее сообщение об ошибке:
Failure/Error: logger_mock.should_receive(:info).with('Starting worker...')
(Double "Logging::Rails").info("Starting worker...")
expected: 1 time
received: 0 times
Я попробовал несколько разных подходов, чтобы получить спецификацию. Это работает, например:
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
Но для установки доступной переменной экземпляра вроде бы кажется, что хвост виляет собаку здесь. (На самом деле, я даже не знаю, почему копирование регистратора на @log приведет к его прохождению.)
Какое хорошее решение для тестирования регистрации?
Ответы
Ответ 1
Пока я согласен, вы вообще не хотите тестировать регистраторы, иногда это может быть полезно.
У меня был успех с ожиданиями на Rails.logger
.
Использование устаревшего синтаксиса should
RSpec:
Rails.logger.should_receive(:info).with("some message")
Использование синтаксиса RSpec newer expect
:
expect(Rails.logger).to receive(:info).with("some message")
Примечание. В спецификациях контроллера и модели вы должны поместить эту строку до того, как сообщение будет зарегистрировано. Если вы поместите его после этого, вы получите сообщение об ошибке следующего вида:
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
Ответ 2
С RSpec 3+ версия
Фактический код, содержащий один вызов Rails.logger.error
:
Rails.logger.error "Some useful error message"
Код спецификации:
expect(Rails.logger).to receive(:error).with(/error message/)
Если вы хотите, чтобы сообщение об ошибке действительно регистрировалось во время выполнения спецификации, используйте следующий код:
expect(Rails.logger).to receive(:error).with(/error message/).and_call_original
Фактический код, содержащий несколько вызовов Rails.logger.error
:
Rails.logger.error "Technical Error Message"
Rails.logger.error "User-friendly Error Message"
Спец код:
expect(Rails.logger).to receive(:error).ordered
expect(Rails.logger).to receive(:error).with(/User-friendly Error /).ordered.and_call_original
Также, если вы хотите просто сопоставить первое сообщение, а не последующие сообщения, вы можете использовать следующее
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
Примечание в приведенной выше настройке варианта .ordered
важно, иначе набор ожиданий начинает сбой.
Рекомендации:
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
Ответ 3
Если ваша цель - проверить возможности ведения журнала, вы можете также рассмотреть возможность проверки вывода на стандартные потоки.
Это избавит вас от насмешливого процесса и проверит, действительно ли сообщения будут в конечном итоге там, где они должны были (STDOUT/STDERR).
С RSpec выходным соглашением (представлен в версии 3.0) вы можете сделать следующее:
expect { my_method }.to output("my message").to_stdout
expect { my_method }.to output("my error").to_stderr
В случае библиотек, таких как Logger
или Logging
, вам может потребоваться использовать output.to_<>_from_any_process
.
Ответ 4
Вместо использования этой строки перед тем, как сообщение будет зарегистрировано:
expect(Rails.logger).to receive(:info).with("some message")
something that triggers the logger...
Вы можете установить регистратор Rails как шпиона и использовать вместо него have_received
:
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