Встроенная поддержка ISO 8061 в RSpec или Minitest

Когда to_json вызывается для объекта в Rails, объекты datetime автоматически преобразуются в стандарт ISO 8601, что является отличным инструментом для отправки данных обратно клиенту.

[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)> 

Однако в RSpec или Minitest необходимо вручную преобразовать время, как показано ниже:

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

Точно так же, как это происходит автоматически в to_json, есть ли способ сделать то же самое в RSpec или Minitest?

Один хакерский способ, который я смог найти:

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

Конечно, это включает в себя преобразование хеша в JSON и его обратный анализ, просто чтобы иметь правильный и сопоставимый формат времени.

🤔 А знаете ли вы, что...
Rails также используется в других известных проектах, таких как GitHub и Airbnb.


1
58
3

Ответы:

Если цель теста — проверить правильность преобразования пары pin_code_sent_at в JSON, вы можете упростить это с помощью have_attributes. Вы можете протестировать отдельные ключи без необходимости менять тест каждый раз при добавлении ключа.

expect(parsed_body).to have_attributes(
  "pin_code_sent_at": user.pin_code_sent_at.utc.iso8601(3)
)

Вы также можете просто просмотреть ожидаемые данные через JSON и посмотреть, совпадают ли они. Это называется «круглым циклом», гарантирующим правильную сериализацию и десериализацию данных. Это нормально, вы не тестируете парсер JSON, вы проверяете, были ли ему переданы правильные данные. Это позволяет избежать ложных сбоев теста при изменении деталей форматирования JSON.

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

Если вы часто этим занимаетесь, вы можете написать собственное средство сопоставления.

RSpec::Matchers.define :have_json_attributes do |expected|
  match do |actual|
    expect(actual).to have_attributes(JSON.parse(expected.to_json))
  end
end

expect(parsed_body).to have_json_attributes(
  pin_code_sent_at: user.pin_code_sent_at
)

(Что-то в этом роде, сейчас я не могу это проверить.)


Решено

Вы столкнулись с этой проблемой, потому что JSON.parse не воссоздает объект времени:

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"

Как видите, значение :key/"key" изменилось с экземпляра Time на экземпляр String. Такое поведение ожидается из документации JSON:

Когда вы «перемещаете» объект, не являющийся строкой, из Ruby в JSON и обратно, у вас есть новая строка вместо объекта, с которого вы начали.

Затем в документации объясняются так называемые дополнения JSON, которые добавляют детали класса в строку JSON для анализа. Но я думаю, что это не вариант, поскольку он существенно изменит сгенерированный JSON и работает только для Ruby.

Однако более простой способ получить сопоставимую структуру — получить JSON-представление объекта времени через as_json из поддержки JSON ActiveSupport. Соответствующий ключ может быть задан в виде буквальной строки:

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

Преобразование моих комментариев в «ответ», поскольку я считаю, что это добавляет некоторый контекст. Для реальных решений, пожалуйста, проверьте другие ответы.

В самой спецификации JSON нет понятия даты и времени, поэтому техническая зависимость от любого заданного представления, вероятно, является неподходящим средством тестирования, если только вы не собираетесь написать собственный сериализатор, реализующий этот стандарт.

Вариации представления

Рельсы

Преобразование объекта Time в стандарт ISO 8601 реализуется Rails по умолчанию. Источник и Источник .

Реализация ISO 8601 в Rails основана исключительно на общем соглашении, рекомендации этого формата в RFC 7493 и на том факте, что этот стандарт хорошо известен и широко используется, что делает его более переносимым, чем большинство других.

Интересно, что если настройка to_json в Rails отключена, вы получите ActiveSupport::JSON::Encoding::use_standard_json_time_format

JSON драгоценный камень

Гем Ruby JSON просто вызывает "%Y/%m/%d %H:%M:%S %z" (для большинства объектов), что в случае объекта to_s не является соответствующим стандартом Time

Это создает дополнительную путаницу, поскольку "%Y-%m-%d %H:%M:%S %z" имеет другое представление DateTime, соответствующее RFC 3339. to_s

О Джей

Драгоценный камень oj использует свой собственный подход, представляя "%Y-%m-%dT%H:%M:%S%z" как Time. Это позволяет сохранить объект в формате json и преобразовать этот json обратно в объекты Ruby, но только если обе стороны используют реализацию этой библиотеки.

и т. д...

Туда и обратно

Как вы можете видеть, из-за отсутствия реального стандарта круговой обмен JSON затруднен, и парсер должен либо реализовать свой собственный вариант представления (например, {\"^t\": precision_seconds_since_the_epoch}), либо его нужно будет спроектировать для обработки любого количества возможных представления, чтобы сначала определить, является ли oj датой, а затем преобразовать ее, что, как вы понимаете, может занять много времени и привести к ошибкам.