Когда 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.
Если цель теста — проверить правильность преобразования пары 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
датой, а затем преобразовать ее, что, как вы понимаете, может занять много времени и привести к ошибкам.