How can I trap errors correctly in a Sidekiq worker when testing with RSpec?

1.3k views Asked by At

I have a relatively simple worker that uses Excon to grab something from the internet. I'm trying to be a good tester and use Webmock to force stubbing of the internet interactions, so that I'm actually testing what the code should do based on various stubbed interactions.

I'm noticing that RSpec isn't catching failures that happen inside the worker. This might be my code, it might be a bug, I'm not sure.

Here's a simple worker example (yes I know rescuing exception is bad, I'll fix that next):

  include Sidekiq::Worker
  sidekiq_options queue: 'fetch_article_content', retry: true, backtrace: true

  def perform(url)
    begin
      Excon.get(url)
    rescue Exception => e
      Rails.logger.warn("error: #{e}")
      nil
    end
  end
end

And here's a simplified RSpec test:

      Sidekiq::Testing.inline!
      work = FetchArticleContentWorker.new
      work.perform("http://google.com")

With Webmock enabled, this results in a failure of Excon (seen in the test.log file):

error: Real HTTP connections are disabled. Unregistered request: ...

However, RSpec thinks this worked just fine:

.

Finished in 0.44487 seconds (files took 5.35 seconds to load)
1 example, 0 failures

I'm not sure what I'm doing wrong here. I would expect the fact that the Sidekiq perform failed to get bubbled up to RSpec as a failure, but that's not the case.

  • Am I not catching this error correctly?
  • Should I be checking something in regard to the status of the job instead of expecting RSpec to catch this error in the worker?
  • Should I be doing something else completely?

Thanks!

2

There are 2 answers

0
aridlehoover On BEST ANSWER

In order for RSpec to see the exception, the code must raise an exception.

You could re-raise the existing exception:

def perform(url)
  begin
    Excon.get(url)
  rescue Exception => e
    Rails.logger.warn("error: #{e}")
    raise e
  end
end

You could wrap the existing exception in one of your own:

class MyFancyException < StandardError; end

def perform(url)
  begin
    Excon.get(url)
  rescue Exception => e
    Rails.logger.warn("error: #{e}")
    raise MyFancyException.new(e)
  end
end

Either works. Both would require some RSpec similar to this:

describe Worker do
  subject(:worker) { described_class.new }

  describe "#perform" do
    subject(:perform) { worker.perform }

    let(:url) { "https://google.com" }

    it "raises exception" do
      expect { perform(url) }.to raise_error(MyFancyException)
    end
  end
end
0
geemus On

I think that from the perspective of rspec there is no error, because it is being captured/handled inside the worker. I would expect the test to fail, as you expect, if you removed that rescue. Conversely, you could check in the test to see if perform returns a non-nil value in order to pass. Does that help?