Tests: How to check if a sidekiq job enqueued another sidekiq job

97 Views Asked by At

I have this two example class, and I want to write a rspec that runs PaymentCheckJob for first time, and if payment_type == 'ticket', I want to check if it enqueued another sidekiq job

class ApplicationJob
  include Sidekiq::Job
  sidekiq_options retry: 1

  def perform(*args)
    # Do something
  end
end

class PaymentCheckJob < ApplicationJob
  def perform(args = '{}')
    args = JSON.parse(args)
    invoice_id = args['invoice_id']
    if invoice.payment_type == 'ticket'
      ::PaymentCheckJob.perform_at(1.hour.from_now, { 'invoice_id': invoice.id }.to_json)
      # ::PaymentCheckJob.perform_in(3600, { 'invoice_id': invoice.id }.to_json)
    else
      invoice.payed_at = Time.now
    end
  end
end
1

There are 1 best solutions below

0
mechnicov On

You can stub this method, write two different contexts: when job is enqueued and when not

Something like this

require 'rails_helper'

RSpec.describe PaymentCheckJob, type: :job do
  describe '#perform' do
    subject(:perform_job) { described_class.new.perform(invoice_id: invoice.id) }

    let(:invoice) { Invoice.create(payment_type: payment_type) }

    before { allow(described_class).to receive(:perform_at) }

    context 'when invoice is ticket' do
      let(:payment_type) { 'ticket' }

      it 'enqueues another PaymentCheckJob' do
        perform_job

        expect(described_class).to have_received(:perform_at)
      end
    end

    context 'when invoice is not ticket' do
      let(:payment_type) { 'not_ticket' }

      it 'does not enqueue another PaymentCheckJob' do
        perform_job

        expect(described_class).not_to have_received(:perform_at)
      end
    end
  end
end

You can also check jobs size like

expect { perform_job }.to change { PaymentCheckJob.jobs.size }.by(1)
expect { perform_job }.not_to change { PaymentCheckJob.jobs.size }

You can also read hints about testing of Sidekiq in the docs


But I don't sure it's good idea to invoke one job from another. Probably you can schedule this job with cron (for example 1 per 5 minutes) and instead of passing some arguments fetch batch of these invoices (for example some scope like Invoice.not_processed) inside the job body. To exclude race condition you can lock these invoices for update. Also to ensure no two jobs run at the same time lock body of perform method with advisory locking (mutex). And of course update invoices if needed. In this case, you will be more in managing the processes, and jobs will not somehow messy invoke each other. I think so