Search code examples
ruby-on-railsrubyrspecminitest

ISO 8061 built-in support in RSpec or Minitest


When to_json is called on an object in Rails, the datetime objects are converted to the ISO 8601 standard automatically which is an excellent tool for sending data back to a client.

[43] pry(main)> { first_name: 'Arslan', date_of_birth: user.created_at }.to_json
=> "{\"first_name\":\"Arslan\",\"date_of_birth\":\"2024-08-21T19:44:17.423Z\"}"
[44] pry(main)> user.created_at
=> Wed, 21 Aug 2024 19:44:17.423106000 UTC +00:00
[45] pry(main)> 

However in RSpec or Minitest, one has to manually convert the time, like the following:

expect(parsed_body).to eq({ pin_code_sent_at: user.pin_code_sent_at.utc.iso8601(3)}.stringify_keys)

Just like it's automatic in to_json, is there a way to accomplish a similar in RSpec or Minitest?

One hacky way I could find:

expect(parsed_body).to eq(JSON.parse({ pin_code_sent_at: user.pin_code_sent_at).to_json)

Of course, it involves converting a hash to JSON, and parsing it back, just to have the time format right & comparable.


Solution

  • You're facing this problem because JSON.parse won't re-create the time object:

    t = Time.parse('2024-08-30T14:30+0000')
    
    data = { key: t }
    #=> {:key=>2024-08-30 14:30:00 +0000}
    
    json = data.to_json
    #=> "{\"key\":\"2024-08-30T14:30:00.000+00:00\"}"
    
    parsed_body = JSON.parse(json)
    #=> {"key=">"2024-08-30T14:30:00.000+00:00"
    

    As you can see, the value for :key / "key" changed from an instance of Time to an instance of String. This behavior is expected, from the JSON docs:

    When you “round trip” a non-String object from Ruby to JSON and back, you have a new String, instead of the object you began with

    The documentation then explains so-called JSON additions which add class specific details to the JSON string for parsing. But I guess this isn't an option as it will significantly alter the generated JSON and only works for Ruby.

    However, an easier way to get a comparable structure is to retrieve the time object's JSON representation via as_json from ActiveSupport's JSON support. The corresponding key can be given as a literal string:

    expect(parsed_body).to eq({ "key" => t.as_json })