diff --git a/lib/rspec/rails/matchers/active_job.rb b/lib/rspec/rails/matchers/active_job.rb index 1a7be83132..217503a52e 100644 --- a/lib/rspec/rails/matchers/active_job.rb +++ b/lib/rspec/rails/matchers/active_job.rb @@ -163,7 +163,29 @@ def at_match?(job) return job[:at].nil? if @at == :no_wait return false unless job[:at] - values_match?(@at, Time.at(job[:at])) + scheduled_at = Time.at(job[:at]) + values_match?(@at, scheduled_at) || check_for_inprecise_value(scheduled_at) + end + + def check_for_inprecise_value(scheduled_at) + return unless Time === @at && values_match?(@at.change(usec: 0), scheduled_at) + + RSpec.warn_with((<<-WARNING).gsub(/^\s+\|/, '').chomp) + |[WARNING] Your expected `at(...)` value does not match the job scheduled_at value + |unless microseconds are removed. This precision error often occurs when checking + |values against `Time.current` / `Time.now` which have usec precision, but Rails + |uses `n.seconds.from_now` internally which has a usec count of `0`. + | + |Use `change(usec: 0)` to correct these values. For example: + | + |`Time.current.change(usec: 0)` + | + |Note: RSpec cannot do this for you because jobs can be scheduled with usec + |precision and we do not know wether it is on purpose or not. + | + | + WARNING + false end def set_expected_number(relativity, count) diff --git a/spec/rspec/rails/matchers/active_job_spec.rb b/spec/rspec/rails/matchers/active_job_spec.rb index 1a15b5cf1a..896cd7db1e 100644 --- a/spec/rspec/rails/matchers/active_job_spec.rb +++ b/spec/rspec/rails/matchers/active_job_spec.rb @@ -225,14 +225,25 @@ def self.name; "LoggingJob"; end }.to have_enqueued_job.at(time) end - skip_freeze_time = method_defined?(:freeze_time) ? false : "#freeze_time is undefined" - it "works with time offsets", skip: skip_freeze_time do - freeze_time do - time = Time.current + it "works with time offsets" do + # note that Time.current does not replicate Rails behavior for 5 seconds from now. + time = Time.current.change(usec: 0) + travel_to time do expect { hello_job.set(wait: 5).perform_later }.to have_enqueued_job.at(time + 5) end end + it "warns when time offsets are inprecise" do + expect(RSpec).to receive(:warn_with).with(/precision error/) + + time = Time.current.change(usec: 550) + travel_to time do + expect { + expect { hello_job.set(wait: 5).perform_later }.to have_enqueued_job.at(time + 5) + }.to raise_error(/expected to enqueue exactly 1 jobs/) + end + end + it "accepts composable matchers as an at date" do future = 1.minute.from_now slightly_earlier = 58.seconds.from_now