5

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!

infused
  • 24,000
  • 13
  • 68
  • 78
Juanito Fatas
  • 9,419
  • 9
  • 46
  • 70

4 Answers4

7

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.

infused
  • 24,000
  • 13
  • 68
  • 78
  • 1
    I wanna test that my job is added to array, and execute 15 mins later. Use `inline!` is not correct. – Juanito Fatas Aug 12 '14 at 02:45
  • Do you want to test if you job is *scheduled* to run 15 minutes later or test if it actually *runs* 15 minutes later? If you want to test if it *runs*, `inline` is your only option. – infused Aug 12 '14 at 02:47
  • Thanks for reply. I want to test it scheduled and actually runs 15 minutes later. – Juanito Fatas Aug 12 '14 at 03:02
  • 18
    You are over testing. Test your functionality, not Sidekiq's functionality. – Mike Perham Aug 15 '14 at 03:13
  • 3
    Can't agree with latest comment. What if I want to protect code from undesired changes? It should be some way to test the delay. – denis.peplin Jun 14 '16 at 13:06
  • I agree with Mike about not testing the the implementation details that make your code work, like Sidekiq. I agree with denis about having to test whether the delay is working or not. – ichigolas Mar 27 '17 at 23:58
4

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"
Max
  • 2,063
  • 2
  • 17
  • 16
2

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.

Siva Gollapalli
  • 626
  • 6
  • 20
1

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
Zoe
  • 27,060
  • 21
  • 118
  • 148