rspecsidekiqwebmockexcon

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


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.

Thanks!


Solution

  • 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