如何测试ActiveSupport::TaggedLogging

How to test ActiveSupport::TaggedLogging

我想测试我在代码中生成的特定日志。虽然测试 Rails.logger 似乎相当简单,但我将其包装在 ActiveSupport::TaggedLogging 中,我在测试时遇到了问题...

这里有两种方法:

  def logger
    @logger = ActiveSupport::TaggedLogging.new(Rails.logger)
  end

  def log(message)
    logger.tagged('MangaDex::Importer', "User ##{@current_user_id}") do
      logger.info message
    end
  end

然后我在我的代码中这样调用它:

    if md_parsed_list.blank?
      log 'List is inaccessible'
      return
    end

现在,在我的 Rspec 测试中,我一直在尝试做这样的事情:

  it 'logs and stops execution if there is nothing to import' do
    expect(Spiders::Mangadex).to receive(:parse!)
      .with(:parse, url: import_url)
      .and_return({})

    expect(ActiveSupport::TaggedLogging).to receive(Rails.logger)
    expect(Rails.logger).to receive(:info).with('List is inaccessible')
    expect(CreateMangaEntries).not_to receive(:call)

    described_class.perform_async(import_url, user.id)
    described_class.drain
  end

我知道我在连接 TaggedLogging 和 Rails.logger 时遗漏了一些步骤,因为调用上面的测试只会引发错误 undefined method 'to_sym' for #<ActiveSupport::Logger:0x00007f8fc545db50>

希望得到一些帮助,在此先感谢!

编辑:

根据@Grzegorz 改进了所涉及的模拟后,测试结果如下所示:

  let(:tagged_logger_double) { instance_double(ActiveSupport::Logger) }

  it 'logs and stops execution if MDList is inaccessible' do
    expect(Spiders::Mangadex).to receive(:parse!)
      .with(:parse, url: import_url)
      .and_return(nil)
    expect(ActiveSupport::TaggedLogging)
      .to receive(:new)
      .with(Rails.logger)
      .and_return(tagged_logger_double)
    expect(CreateMangaEntries).not_to receive(:call)
    expect(tagged_logger_double).to receive(:info).with('List is inaccessible')

    described_class.perform_async(import_url, user.id)
    described_class.drain
  end

由于 ActiveSupport::TaggedLogging 的实例为 ActiveSupport::Logger,因此我不得不稍微更改双精度数。

我能够解决最初的异常,但仍然无法测试 receive(:info),因为 tagged 似乎丢失了: #<InstanceDouble(ActiveSupport::Logger) (anonymous)> received unexpected message :tagged with ("MangaDex::Importer", "User #2626")

但是,当我也尝试对该方法进行存根时:allow(tagged_logger_double).to receive(:tagged),我得到这样的方法不存在的错误:the ActiveSupport::Logger class does not implement the instance method: tagged,但我仍然遇到失败的测试。

您已将一个对象传递给 .to receive 方法,它需要一个方法名称(字符串或符号)。这是你错误的根源,下面这个对我来说更有意义:

expect(ActiveSupport::TaggedLogging)
  .to receive(:new)
  .with(Rails.logger)
  .and_return(tagged_logger_double)

如果您这样定义记录器,那么您将拥有对规范中记录器的完全访问权限

let(:tagged_logger_double) { instance_double(ActiveSupport::TaggedLogging) }

并设定您的期望

expect(tagged_logger_double).to receive(:info).with('List is inaccessible')

Grzegorz 正确回答了你问题的第一部分,所以从他停下的地方继续...

你是对的,你需要存根 tagged。你得到方法未实现错误的原因是因为 ActiveSupport::Logger 没有实现 tagged。它在被 ActiveSupport::TaggedLogging.

包裹时被扩展

在此处查看构造方法定义:https://api.rubyonrails.org/classes/ActiveSupport/TaggedLogging.html#method-c-new

我不确定在这种情况下如何使用验证替身。一种选择是不这样做,只使用 double.

另一个问题是你没有正确地存根 tagged。您需要存根以便它屈服于块。

最后,您需要记住 logger 方法的 return 值,因为您只希望 ActiveSupport::TaggedLogging 收到一次 new

综上所述,我编写了以下测试:

require "rails_helper"
require "custom_logger"

RSpec.describe "CustomLogger" do
  let(:tagged_logger_double) { double("Tagged logger") }

  it "logs" do
    expect(ActiveSupport::TaggedLogging)
      .to receive(:new)
      .with(Rails.logger)
      .and_return(tagged_logger_double)

    allow(tagged_logger_double)
      .to receive(:tagged)
      .and_yield

    expect(tagged_logger_double).to receive(:info).with("message")

    CustomLogger.new.log("message")
  end
end

这通过了以下代码,我认为它与您的示例中的代码非常相似:

class CustomLogger
  def log(message)
    logger.tagged("Tag") do
      logger.info message
    end
  end

  def logger
    @_logger ||= ActiveSupport::TaggedLogging.new(Rails.logger)
  end
end

我需要测试已记录的标签和消息。 此代码对我有用。

let(:logger) { ActiveSupport::TaggedLogging.new(Rails.logger) }

before do
    allow(logger).to receive(:tagged).and_call_original
    allow(logger).to receive(:info).and_call_original
    allow(ActiveSupport::TaggedLogging).to receive(:new).and_return(logger)
end

it 'logs with correct tags' do
  expect(logger).to have_received(:tagged).with(tags)
end

it 'logs with correct message' do
  expect(logger).to have_received(:info).with(message)
end