Testing Delayed Jobs

Delayed Job is a Ruby gem that encapsulates the common pattern of asynchronously executing longer tasks in the background.

I use it frequently because it's a very easy way to send long tasks to the background or schedule some task for the future.

In a testing environment you can switch off Delayed Jobs and every task will be executed immediately but sometimes that's not enough if you're scheduling some task that should run in a specific time in the future.

Anyway, it gives some extra peace of mind if my test is scheduling a background job and running it when it’s supposed to. In order to do this I use Timecop to set the current time in my testing environment and an auxiliary method that fetches and runs scheduled jobs.

The first thing to do is installing Timecop. You can do that by simply adding it to your Gemfile and run bundle.

group :test do  
  # ...
  gem 'timecop'
end  

Now let’s create the method to run the delayed jobs. I usually create a module called DelayedJobHelper and put it in spec/support (yes, I’m using RSpec here, if you use a different testing framework, it probably won’t be too hard to make the transition).

module DelayedJobHelper  
  def perform_jobs    
    Delayed::Job.all.to_a.each do |job|
      if job.run_at <= Time.now
        job.invoke_job
        job.destroy
      end      
    end
  end
end  

The method perform_jobs will run through every delayed job in the database, check if it's scheduled to run now or sometime in the past and if that’s true, invoke the job and finally destroy it. To get access to this method in your spec files, you should include the following lines in your spec_helper.rb.

# ...
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

RSpec.configure do |config|  
  # ...
  config.include(DelayedJobHelper)
end  

Now that we’re all set up let’s see an example of how to test a delayed job.

Let’s say we have a model called Event. An event has a date, a name and many subscribers. Also, an event, after creation, schedules a job to send a notification to every subscriber on the day before the event.

class Event < ActiveRecord::Base  
  attr_accessible :name, :date

  has_many :subscribers

  after_create :notify_subscribers

  def notify_subscribers
    self.subscribers.find_each do |s|
      s.send_notification :event_tomorrow, self
    end
  end
  handle_asynchronously 
    :notify_subscribers, 
    run_at: Proc.new { |e| e.date.beginning_of_day - 1.day }
end  

Now, let's build a spec for the Event model.

require 'spec_helper'

describe "Event" do  
  it "notyfies every subscriber on the day before the event" do
    # Create an event
    event = Event.create date: Time.now + 1.month, name: 'Super Awesome Event'

    # Add some subscribers
    10.times{ |i| event.subscribers.create email: "subscriber#{i}@mail.fake" }

    Timecop.freeze(event.date.beggining_of_day - 1.day) do      
      perform_jobs
      event.subscribers.find_each do |s|
        expect(s.notifications.exists? type: :event_tomorrow, event: event).to be_true
      end
    end
  end
end  

`

Ok! It’s done. Now we can test if any job is being scheduled correctly and executing at the right moment.