Here is the actual code that gets called:

def update_timestamp(photo, column)

stamp = [max_timestamp(column) + MIN_GAP, DateTime.current].max

photo.update(column => stamp)

end

We wanted to make sure there’s a minimum time gap between timestamps, so we set stamp to either the maximum existing value of the column ( max_timestamp(column) ) plus a gap interval ( MIN_GAP ), or to DateTime.current , whichever one is later. MIN_GAP is set to 30.minutes — that mysterious amount of time that our test is failing by! So it looks like there’s a problem with the test, but why is it passing the majority of the time?

In the test, max_timestamp is stubbed out to return DateTime.current for simplicity, so stamp in the update_timestamp method should always be set to DateTime.current + MIN_GAP . Which means that the stub expectation set above in the test code should be failing, but is false-positive passing:

expect(photo).to receive(:update).with(curated_at: DateTime.current)

When a method is stubbed with an expected input, RSpec compares the actual input to the expectation using === . As it turns out, two DateTime s are considered equivalent by === as long as the date component is equal, completely ignoring the time component!

a = DateTime.parse("8pm")

=> Tue, 19 Mar 2019 20:00:00 +0000

b = DateTime.parse("11pm")

=> Tue, 19 Mar 2019 23:00:00 +0000

a === b

=> true

So why is DateTime#=== only comparing dates? The answer is actually easily revealed using pry:

pry(main)> cd DateTime.current

pry(#<DateTime>):1> ? ===



From: ext/date/date_core.c (C Method):

Owner: Date

Visibility: public

Signature: ===(arg1)

Number of lines: 12



Returns true if they are the same day.



Date.new(2001,2,3) === Date.new(2001,2,3)

#=> true

Date.new(2001,2,3) === Date.new(2001,2,4)

#=> false

DateTime.new(2001,2,3) === DateTime.new(2001,2,3,12)

#=> true

DateTime.new(2001,2,3) === DateTime.new(2001,2,3,0,0,0,'+24:00')

#=> true

DateTime.new(2001,2,3) === DateTime.new(2001,2,4,0,0,0,'+24:00')

#=> false

The === method is implemented in ext/date/date_core.c as a method on the Date class, which DateTime inherits from, but does not override (it’s not really evident why DateTime doesn’t have its own implementation). Therefore, DateTime#=== is identical to Date#=== , which doesn’t know about time.

RSpec actually had a deprecation warning about this very thing in version 2.99, but it looks like the warning never made it into RSpec 3+ for whatever reason, and the maintainers decided against adding special behaviour for it.

With this information, we can fix the test and remove the false positive, by using Time instead of DateTime , as RSpec suggests, and fix the expectation to incorporate the 30 minute gap.