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?
I tend to avoid mocking in general.
I had a similar problem and here's how I achieved it:
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
ActiveSupport::Notifications
as intendedActiveSupport::Notifications::Event
wrapper gives you nice extras like #duration
ActiveSupport::Notifications.subscribe(/asd/)
to do partial matches on event names@events
array@events
on teardown