Search code examples
rubyrspecbugsnag

How do you test custom Bugsnag meta_data in Ruby?


How do you test custom Bugsnag meta_data (in Ruby, with Rspec)?

The code that I want to test:

def do_something
  thing_that_could_error
rescue => e
  Bugsnag.notify(e) do |r|
    r.meta_data = { my_extra_data: "useful info" }
  end
end

The test I want to write:

context "when there's an error" do
  it "calls Bugsnag with my special metadata" do
    expect(Bugsnag).to receive(:notify) # TODO test meta_data values contain "my useful info"
    expect do
      do_something() # exception is thrown and rescued and sent to Bugsnag
    end.not_to raise_error
  end
end

I am using:

The data inside of the meta_data variable is considerably more complicated than in this tiny example, which is why I want to test it. In a beautiful world, I would extract that logic to a helper and test the helper, but right now it is urgent and useful to test in situ.

I've been looking at the inside of the Bugsnag gem to figure this out (plus some Rspec-fu to capture various internal state and returned data) but at some point it's a good idea to ask the internet.


Solution

  • Since the metadata is complicated, I'd suggest simplifying it:

    def do_something
      thing_that_could_error
    rescue => e
      Bugsnag.notify(e) do |r|
        r.meta_data = error_metadata(e, self, 'foo')
      end
    end
    
    # I assumed that you'd like to pass exception and all the context
    def error_metadata(e, object, *rest)
      BugsnagMetadataComposer.new(e, object, *rest).metadata
    end
    

    So now you can have a separate test for BugsnagMetadataComposer where you have full control (without mocking) over how you initialize it, and test for metadata output.

    Now you only have to test that BugsnagMetadataComposer is instantiated with the objects you want, metadata is called and it returns dummy hash:

    let(:my_exception) { StandardError.new } 
    let(:mock_metadata) { Hash.new } 
    
    before do
      # ensure thing_that_could_error throws `my_exception`
    expect(BugsnagMetadataComposer)
      .to receive(new)
      .with(my_exception, subject, anything)
      .and_return(mock_metadata)
    end
    

    And the hard part, ensure that metadata is assigned. To do that you can cheat a little and see how Bugsnag gem is doing it

    Apparently there's something called breadcrumbs:

      let(:breadcrumbs) { Bugsnag.configuration.breadcrumbs }
    

    Which I guess has all the Bugsnag requests, last one on top, so you can do something similar to https://github.com/bugsnag/bugsnag-ruby/blob/f9c539670c448f7f129a3f8be7d412e2e824a357/spec/bugsnag_spec.rb#L36-L40

    specify do 
      do_something()
    
      expect(breadcrumbs.last.metadata).to eq(expected_metadata)
    end
    

    And for clarity, the whole spec would look a bit like this:

    let(:my_exception) { StandardError.new } 
    let(:mock_metadata) { Hash.new } 
    
    before do
      # ensure thing_that_could_error throws `my_exception`
      expect(BugsnagMetadataComposer)
        .to receive(new)
        .with(my_exception, subject, anything)
        .and_return(mock_metadata)
    end
    
    specify do 
      do_something()
    
      expect(breadcrumbs.last.metadata).to eq(expected_metadata)
    end