Rspec-rails: Strange behavior with have_enqueued_job

Created on 4 Sep 2018  路  9Comments  路  Source: rspec/rspec-rails

Hello everyone !
I've got to report a strange behavior that I observed today with the use of have_enqueud_job

Ruby version: ruby 2.4.1
Rails version: 4.2.10
Rspec version: 3.7

Observed behaviour

Consider the following code :

class MyJob < ActiveJob::Base
  def perform(param)
    puts "Hello world ! #{param}"
  end
end

class JobLauncher
  def launch
    MyJob.perform_later("first job")
    MyJob.perform_later("second job")
  end
end

RSpec.describe JobLauncher do
  describe "#launch" do
    it "works" do
      expect {
        JobLauncher.new.launch
      }.to have_enqueued_job(MyJob)
    end
  end
end

This spec is failing because two jobs have been enqueued with the following message :

expected to enqueue exactly 1 jobs, but enqueued 2

There is nothing wrong with that and that's the behavior expected.
But when you change the expectation line with :

      expect {
        JobLauncher.new.launch
      }.to have_enqueued_job(MyJob).with("first job")

then the spec does not fail anymore, even though there is still two jobs that have been enqueued.

Expected behaviour

I find this difference of behavior very strange. I would have expected that have_enqueued_job(Job).with(params) would behave the same as without the with, just checking the job params extra.

Maybe it is not a bug but a feature. If so, is there a way to check that only one job have been enqueued with RSpec ?

Thank you

reporter-submitted-pr

All 9 comments

Hello

Thanks for your detailed report.

Just a quick look. It seems we are not validating class only job count. https://github.com/rspec/rspec-rails/blob/v3.8.0/lib/rspec/rails/matchers/active_job.rb#L188

I will try to have a look even if I don't have a lot of time at the moment. Feel free to open a PR if you can dig into this subject.

The issue is we don't have a registry of enqueued jobs used / expected unlike how we do with expectations of methods etc, so we don't have global knowledge of whats expected, so "unexpected" jobs are just allowed through, this might take some work to fix.

As a work around this should work for now:

expect {
        JobLauncher.new.launch
}.to have_enqueued_job(MyJob) { |args| expect(args).to eq ['first job'] }

We could make the default expected number relativity :at_least- This breaks 3 specs, but only the expected error message. This changes the default behavior of the HaveEnqueuedJob matcher, but that can be restored by chaining the #exactly method on the matcher if someone is relying on the default exactly.

I've opened PR https://github.com/rspec/rspec-rails/pull/2033 to continue the conversation

just to add my two cents. I found it very unexpected that this is using an exactly 1 match. it should be checking at least 1 not failing if there are more

@SampsonCrowley the behaviour you are looking for can be provided if you add at_least(:once), as has been discussed on #1973, the current behaviour is in line with all of our other matchers

@JonRowe I'm aware, thank you. I'm just adding my opinion for the next major release.

I'm saying the name of the rule doesn't match expected behavior, by English logic. If two of a job are enqueued, then the job has been enqueued. The default should be an at_least matcher, and should be able to chain an exactly matcher if desired. Or even better would be a config option to default these types of scenarios to the user's preference.

@SampsonCrowley The logic comes from the fact that duplicate calls are more likely to be an error than a deliberate decision, thus when its deliberate you can tell us, removing silent errors. Its not going to be changed.

Is there anything actionable left?

Was this page helpful?
0 / 5 - 0 ratings