Testing sidekiq perform_in with RSpec 3

11.4k views Asked by At

RSpec 3 and sidekiq 3.2.1. And I have setup sidekiq and rspec-sidekiq properly.

Suppose I have a worker called WeatherJob, which will change the weather status from sunny to rainy:

class WeatherJob
  include Sidekiq::Worker

  def perform record_id
    weather = Weather.find record_id

    weather.update status: 'rainy'
  end
end

I use this worker like this:

WeatherJob.perform_in 15.minutes, weather.id.

In the spec, I use Timecop to mock time:

require 'rails_helper'

describe WeatherJob do
  let(:weather) { create :weather, status: 'sunny' }
  let(:now)     { Time.current }

  it 'enqueue a job' do
    expect {
      WeatherJob.perform_async weather.id
    }.to change(WeatherJob.jobs, :size).by 1
  end

  context '15 mins later' do
    before do
      Timecop.freeze(now) do
        Weather.perform_in 15.minutes, weather.id
      end
    end

    it 'update to rainy' do
      Timecop.freeze(now + 16.minutes) do
        expect(weather.status).to eq 'rainy'
      end
    end
  end
end

I could see there is job in Weather.jobs array. And time is correctly 16 mins after. But it did not execute the job? Any advices? Thanks!

4

There are 4 answers

0
Siva Gollapalli On

If you want to test the job whether it should executes 15 minutes later or not then you should split you test cases into two parts. First part, you should test that whether it inserts job which would be active in 15 minutes(using mocks). Second part, whether the job has been executed properly or not.

6
infused On

Sidekiq has three testing modes: disabled, fake, and inline. The default is fake, which just pushes all jobs into a jobs array and is the behavior you are seeing. The inline mode runs the job immediately instead of enqueuing it.

To force Sidekiq to run the job inline during the test, wrap your test code in a Sidekiq::Testing.inline! block:

before do
  Sidekiq::Testing.inline! do
    Timecop.freeze(now) do
      Weather.perform_in 15.minutes, weather.id
    end
  end
end

For more info on testing Sidekiq, refer to the official Testing wiki page.

0
Max On

Do it in two steps. First test that the job was scheduled, then execute a job inline without time delay. Here is an example

it "finishes auction  (async)" do
  auction = FactoryGirl.create(:auction)
  auction.publish!

  expect(AuctionFinishWorker).to have_enqueued_sidekiq_job(auction.id).at(auction.finishes_at)
end

it "finishes auction  (sync)" do
  auction = FactoryGirl.create(:auction)
  auction.publish!

  Sidekiq::Testing.inline! do
    AuctionFinishWorker.perform_async(auction.id)
  end

  auction.reload
  expect(auction).to be_finished
end

have_enqueued_sidekiq_job method is coming from rspec-sidekiq gem. They have active development going on at develop branch. Make sure you include it like that

  gem 'rspec-sidekiq', github: "philostler/rspec-sidekiq", branch: "develop"
0
Prakash Sanyasi On

Weather.drain can be a hack for issue

require 'rails_helper'

describe WeatherJob do
  let(:weather) { create :weather, status: 'sunny' }
  let(:now)     { Time.current }

  it 'enqueue a job' do
    expect {
      WeatherJob.perform_async weather.id
    }.to change(WeatherJob.jobs, :size).by 1
  end

  context '15 mins later' do
    before do
      Timecop.freeze(now) do
        Weather.perform_in 15.minutes, weather.id
      end
    end

    it 'update to rainy' do
      Timecop.freeze(now + 16.minutes) do
        Weather.drain
        expect(weather.status).to eq 'rainy'
      end
    end
  end
end