Testing Sidekiq in Ruby on Rails: A Comprehensive Guide

Testing Sidekiq in Ruby on Rails: A Comprehensive Guide

Introduction

Sidekiq is a powerful tool for handling asynchronous jobs in Ruby. It can be easily integrated with Ruby on Rails applications to streamline background tasks. With Ruby on Rails, there are two main options for using Sidekiq:

  1. Integrate Sidekiq with Active Job and utilize the helpers provided by Rails

  2. Use plain Sidekiq without Active Job

In this article, we will focus on using Sidekiq without Active Job, which can be 2-20 times faster than using it with Active Job. We will cover how to configure Sidekiq with Ruby on Rails application, how to generate jobs, and how to test them. Let's get started!

Config Sidekiq in a Ruby on Rails Application

  1. Add sidekiq to the Gemfile.
# Gemfile
gem 'sidekiq'

group :test do
  gem 'rspec-sidekiq'
end

2. Configure the queue adapter for Rails:

# config/application.rb
config.active_job.queue_adapter = :sidekiq

With this configuration, Sidekiq is now set up for your Ruby on Rails application. Next, let's generate our first job:

rails generate sidekiq:job example

Generate a New Job

We will create a simple service that runs a job asynchronously and a job that updates an employee's position:

# app/services/example.rb
class Example
  def initialize(employee_id)
    @employee_id = employee_id
  end

  def call
    ExampleJob.perform_async(@employee_id)
  end
end
# app/sidekiq/example_job.rb
class ExampleJob
  include Sidekiq::Job

  def perform(employee_id)
    employee = Employee.find(employee_id)

    employee.update!(position: 'CEO')
  end
end

Sidekiq testing

With Sidekiq, we have three strategies for testing our code:

  1. Fake mode (default): Jobs are pushed into an array instead of Redis.

  2. Inline mode: Code is executed synchronously.

  3. Disable mode: Jobs are pushed into Redis.

For service testing, I recommend using the default fake mode. For job testing, I recommend using inline mode, as it tests the exact behavior of the worker.

Fake Testing

subject(:job) { ExampleJob.perform_async(employee_id) }

it 'queues the job' do
    expect { job }.to change(Sidekiq::Queues['default'], :size).by(1)
end

it 'queues the job2' do
    expect { job }.to change(ExampleJob.jobs, :size).by(1)
end

it 'queues the job3 with rspec-sidekiq helper' do
    job
    expect(described_class).to have_enqueued_sidekiq_job(employee.id)
end

Now, let's execute the code and check if the worker's code is working as expected. To do this, we will use the drain method, which processes all jobs:

expect do
    job
    described_class.drain
end.to change { employee.reload.position }.from('resource').to('CEO')

We can also execute the job using the perform_inline method, which will execute the code synchronously:

it 'raise errors2' do
    expect { described_class.perform_inline(employee_id) }.to raise_error(ActiveRecord::RecordNotFound)
end

Lastly, we can use the RSpec hook with custom metadata:

it 'raise errors3', :inline_sidekiq_testing do
    expect { job }.to raise_error(ActiveRecord::RecordNotFound)
end
---
rails_helper.rb

config.around(:each, :inline_sidekiq_testing) do |example|
    Sidekiq::Testing.inline! do
        example.run
    end
end

Testing with Disable Mode

This mode can be useful for integration tests when we want to check if the job was queued in Redis.

it 'enqueue job' do
    Sidekiq::Testing.disable! do
        expect { service }.to change { Sidekiq::Queue.new.size }.by(1)
    end
end

You can refactor the above code as follows:

config.around(:each, :disable_sidekiq_testing) do |example|
    Sidekiq::Testing.disable! do
      example.run
    end
  end

  it 'enqueue job2', :disable_sidekiq_testing do
      expect { service }.to change { Sidekiq::Queue.new.size }.by(1)
  end

You can also test it in this way:

it 'enqueue job3', :disable_sidekiq_testing do
  expect { service }.to change {
    Sidekiq::Queue.new.select do |job|
      job.klass == 'ExampleJob'
      job.args == [employee_id_param]
      job.queue == 'default'
    end.size
  }.by(1)
end

Tips and Sidekiq Web UI

At the end of this guide, I want to introduce you to the Sidekiq Web UI. You can implement it by adding the following your routes:

# config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  mount Sidekiq::Web => '/sidekiq'
  root 'pages#home'
end

With the Sidekiq Web UI, you can monitor the status of your jobs and gain insights into the performance of your background tasks.

Now you have a comprehensive guide on how to test Sidekiq in a Ruby on Rails application. Good luck and happy testing!

Resources

https://github.com/sidekiq/sidekiq/wiki/Testing

https://gist.github.com/mateuszbialowas/b9944a3c0a11c80e25aceb4e0483afe1