35

I am trying to run rspecs for a custom delayed job (GetPage::GetPageJob), but I have a problem.

When I run them, the jobs are well enqueued (that is to say, well inserted in the delayed_jobs table), but they are not processed by the job worker. Indeed, after launching "rake jobs:work RAILS_ENV=test" in a first terminal, and after running the specs in a second terminal, I don't see any output from the job worker in the first terminal.

On the other hand, the jobs are well processed if I enqueue them via "script/console test". So I'm a bit confused.

With both the specs and the script/console, the line I use to enqueue my jobs is :

Delayed::Job.enqueue GetPage::GetPageJob.new("http://cnn.com")

Any idea ?

Jerome
  • 359
  • 1
  • 3
  • 4

4 Answers4

98

The simplest way to test queued Delayed::Job tasks in RSpec is to run them in real time. Simply add the following line to your RSpec tests:

Delayed::Worker.delay_jobs = false

This will cause your jobs to be processed immediately upon enqueuing, not in a separate thread. This is usually what you want for testing, since it's deterministic.

Two caveats

  • If you are trying to test for timing errors, race conditions, etc, this approach won't help (since the jobs are processed in the same thread as RSpec)

  • The current version of delayed_job (2.1.4) has a minor bug in which the callback hooks (enqueue, before, success, error, failure) don't get called when Delayed::Worker.delay_jobs is set to false.

Two Workarounds

If you need to test the callback hooks I know of two workarounds:

  • Fetch the latest master branch from github. (I haven't tried that because I need a stable version)

  • Instead of setting Delayed::Worker.delay_jobs = false, explicitly call DJ's run mechanism in your test code as follows:

    successes, failures = Delayed::Worker.new.work_off

That will process whatever is in the job queue (again, in the same thread as the RSpec tests) and return two numbers: the # of jobs that succeeded and the # of jobs that failed. I currently use this approach and it does everything I need.

fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • 6
    your answer is quite smart, complete and well documented :). Thanks, you've made my day :) – Dorian Aug 26 '11 at 12:50
  • what about :attempts ? How do you test that? – oma Mar 09 '12 at 11:55
  • @oma: Perhaps I misunderstand you, but the [successes, failures] return values takes :attempts into account: a job doesn't actually fail until the number of :attempts is exceeded. – fearless_fool Mar 12 '12 at 23:59
  • didn't want to highjack this thread. Maybe I'll write up a Q. I'm setting the max_attempts for each job, implementing the methods error and reschedule (that DJ look for with responds_to?). I'm having a hard time testing that it does more attempts... – oma Mar 16 '12 at 09:56
  • 2
    syntax isn't quite correct, no [ ] around successes and failures for assignment `successes, failures = Delayed::Worker.new.work_off` – dB. May 17 '12 at 19:52
  • @dblock: Right you are - I'm editing the response to fix it. (Amazing nobody commented before!) – fearless_fool May 21 '12 at 05:37
  • Probably worth mentioning potential [issues with callbacks](http://artsy.github.io/blog/2012/08/16/testing-with-delayed-jobs/) – brntsllvn Aug 02 '15 at 17:40
3

In the past, I've tried to do an end-to-end test of logic -> delayed-job -> perform job, and it was too many things. I think rather than test that full sweet using RSpec, you could focus on testing each aspect.

So, test that a job gets inserted. Then, have another test that tests what should happen when a job is executed.

Alternatively, mock out the delayed-job, so that when you enqueue a job, it executes it right away.

Jesse Wolgamott
  • 40,197
  • 4
  • 83
  • 109
  • I found your last sentence quite useful; I simply do either, e.g. `UserMailer.stub(delay: UserMailer)` or `obj.stub(delay: obj)` and my tests truck along happily. While the suggestion to do this in the config `Delayed::Worker.delay_jobs = !Rails.env.in?(['development', 'test'])` would work too, I have a whole bunch of legacy tests that are testing the delayed jobs themselves that I don't want to update right now. Thanks! – aec Feb 05 '14 at 20:52
3

You'll need to kick off the worker process from inside your tests rather than from another process. Try:

worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true)
worker.work_off
nmunson
  • 902
  • 8
  • 11
1

I use the config option to run the jobs in real time:

# config/initializers/delayed_job_config.rb
Delayed::Worker.delay_jobs = !Rails.env.test?
rjurado01
  • 5,337
  • 3
  • 36
  • 45