J'essaie de tester que l'enregistreur Rails reçoit des messages dans certaines de mes spécifications. J'utilise Gemme de journalisation .
Disons que j'ai une classe comme celle-ci:
class BaseWorker
def execute
logger.info 'Starting the worker...'
end
end
Et une spécification comme:
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
J'obtiens le message d'échec suivant:
Failure/Error: logger_mock.should_receive(:info).with('Starting worker...')
(Double "Logging::Rails").info("Starting worker...")
expected: 1 time
received: 0 times
J'ai essayé plusieurs approches différentes pour faire passer la spécification. Cela fonctionne par exemple:
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
Mais avoir à configurer une variable d'instance accessible comme celle-ci semble que la queue remue le chien ici. (En fait, je ne sais même pas pourquoi la copie de l'enregistreur dans @log le ferait passer.)
Quelle est une bonne solution pour tester la journalisation?
Bien que je convienne que vous ne voulez généralement pas tester les enregistreurs, il peut parfois être utile.
J'ai eu du succès avec des attentes sur Rails.logger
.
Utilisation de la syntaxe obsolète should
de RSpec:
Rails.logger.should_receive(:info).with("some message")
Utilisation de la nouvelle syntaxe expect
de RSpec:
expect(Rails.logger).to receive(:info).with("some message")
Remarque: Dans les spécifications du contrôleur et du modèle, vous devez mettre cette ligne avant le message est enregistré. Si vous le mettez après, vous obtiendrez un message d'erreur comme celui-ci:
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
Avec la version RSpec 3+
Code réel contenant une seule invocation de Rails.logger.error
:
Rails.logger.error "Some useful error message"
Code de spécification:
expect(Rails.logger).to receive(:error).with(/error message/)
Si vous souhaitez que le message d'erreur soit réellement enregistré pendant l'exécution de la spécification, utilisez le code suivant:
expect(Rails.logger).to receive(:error).with(/error message/).and_call_original
Code réel contenant plusieurs invocations de Rails.logger.error
:
Rails.logger.error "Technical Error Message"
Rails.logger.error "User-friendly Error Message"
Code de spécification:
expect(Rails.logger).to receive(:error).ordered
expect(Rails.logger).to receive(:error).with(/User-friendly Error /).ordered.and_call_original
Aussi si vous vous souciez de faire correspondre le premier message et non les messages suivants, vous pouvez utiliser les suivants
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
Remarque dans le paramètre de variation ci-dessus .ordered
est important sinon les attentes définies commencent à échouer.
Les références:
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
Si votre objectif est de tester la fonctionnalité de journalisation, vous pouvez également envisager de vérifier la sortie vers des flux standard.
Cela vous épargnera le processus de moquerie et testera si les messages finiront réellement là où ils étaient censés (STDOUT/STDERR).
Avec RSpec's output matcher (introduit dans 3.0), vous pouvez faire ce qui suit:
expect { my_method }.to output("my message").to_stdout
expect { my_method }.to output("my error").to_stderr
Dans le cas de bibliothèques telles que Logger
ou Logging
, vous devrez peut-être utiliser output.to_<>_from_any_process
.