1

I'm making a Rspec test that checks if ActiveSupport::Notification.instrument was called with some parameters.

The thing is that in order to make this test a need FactoryBot to build some objects, but when I try to spy on ActiveSupport::Notification.instrument I always get an error:

ActiveSupport::Notifications received :instrument with unexpected arguments
         expected: (:asd)
              got: ("factory_bot.run_factory", {:factory=>#<FactoryBot::Factory:0x005569b6d30, @al... nil, dispatch: nil, distribution_state: 2, main_category_id: nil>}, :strategy=>:build, :traits=>[]})

It seems that FactoryBot calls activesupport so when I mock it for my test purpose I end up mocking it too far...

code example:

class:

class SomeObject
    def initialize(something)
        #some code
    end

    def my_method
        ActiveSupport::Notifications.instrument :asd
    end
end

spec:

describe "#my_method" do
    let(:some_object) { build :some_object }
    before do
      allow(ActiveSupport::Notifications).to receive(:instrument).with :asd
    end

    it "calls notifier" do
      described_class.new(some_object).my_method

      expect(ActiveSupport::Notifications).to have_received(:instrument).with :asd
    end
  end

How can I just mock my call and not FactoryBot's .

I only manage that with one more allow before the one that mocks :asd:

 allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original

Is there another(better) way?

4

1 回答 1

1

一般来说,我倾向于避免嘲笑。

我有一个类似的问题,这是我实现它的方法:

  describe "#my_method" do
    let(:some_object) { build :some_object }

    before { record_events }

    it "calls notifier" do
      described_class.new(some_object).my_method

      # Make sure your event was triggered
      expect(events.map(&:name)).to include('asd')

      # Check number of events
      expect(events).to be_one

      # Check contents of event payload                  
      expect(events.first.payload).to eq({ 'extra' => 'context' })

      # Even check the duration of an event
      expect(events.first.duration).to be < 3
    end

    private

    attr_reader :events

    def record_events
      @events = []
      ActiveSupport::Notifications.subscribe(:asd) do |*args| #
        @events << ActiveSupport::Notifications::Event.new(*args)
      end
    end
  end

相对于 mocking 的优势

  • 没有更多奇怪的副作用
  • ActiveSupport::Notifications按预期使用
  • ActiveSupport::Notifications::Event包装器为您提供了不错的附加功能,例如#duration
  • 轻松检查被触发的其他事件
  • 仅查看与名称匹配的事件的能力 - 用于ActiveSupport::Notifications.subscribe(/asd/)对事件名称进行部分匹配
  • 更好的可读性 - 检查事件数组更具可读性

嘲笑的缺点

  • 更多代码
  • 改变@events数组
  • 如果您不明确,测试之间可能存在依赖@events关系teardown
于 2019-01-29T12:00:35.643 回答