Search code examples
datetimeruby-on-rails-4utciso

Rails 4: `Time.now.utc.iso8601` sometimes off by 1 second in specs


I'm building a Rails 4 API with JSON, and am returning the updated_at attribute as UTC timezone in ISO format.

record.updated_at # => 2015-04-14 10:01:37 -0400 record.updated_at.utc.iso8601 # => "2015-04-14T14:01:37Z"

However, my rspec specs will occasionally fail intermittently when the updated_at is off by 1 second:

# records_controller_spec.rb
RSpec.describe RecordsController do
  describe "update - PUT #update" do
    record = FactoryGirl::create(:record, value: "original")
    record_params = { value: "updated" }

    xhr :put, api_v1_record_path(record), record_params

    # Uses ActiveModelSerializer
    # json = JSON.parse(response.body)
    expect(response).to have_http_status(:ok)
    expect(json["updated_at"]).to eq(record.updated_at.utc.iso8601)
  end
end

# app/serializers/record_serializer.rb
class RecordSerializer < ActiveModel::Serializer
  attributes :id, :created_at, :updated_at, :value

  def created_at
    object.created_at.utc.iso8601
  end

  def updated_at
    object.updated_at.utc.iso8601
  end
end

# Running rspec...
Failure/Error: expect(json["updated_at"]).to eq(record.updated_at.utc.iso8601)

       expected: "2015-04-14T13:59:35Z"
            got: "2015-04-14T13:59:34Z"

       (compared using ==)

If I run the spec again, it passes many times, and then randomly it will fail again, with the time comparison being off by 1 second again.

Is there anyway to ensure consistent datetime conversions in the specs?

The biggest issue is that automated deploys with a CI server will randomly fail if the rspec suite randomly fails if a spec just happens to be on the fence of a given second.


Solution

  • The solution was I wasn't reloading the object after the update actions, thus I was getting a "dirty" updated_at, and it failed sporadically because the tests happened so fast.

    RSpec.describe RecordsController do
      describe "update - PUT #update" do
        record = FactoryGirl::create(:record, value: "original")              
        record_params = { value: "updated" }
    
        # record.updated_at => "2015-06-23 22:30:00"
    
        xhr :put, api_v1_record_path(record), record_params
    
        # record.updated_at => "2015-06-23 22:30:00"
    
        # SOLUTION CODE (notice timestamp updated below)
        record.reload
    
        # record.updated_at => "2015-06-23 22:30:01"
    
    
        # Uses ActiveModelSerializer
        # json = JSON.parse(response.body)
        expect(response).to have_http_status(:ok)
        expect(json["updated_at"]).to eq(record.updated_at.utc.iso8601)
      end
    end
    

    I found that for any of my specs that altered/updated a record, I would have to reload the object to get the proper timestamps values. I don't have better general approach than that.

    If someone has a cleaner solution, feel free to post it and I'll accept it over mine.