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

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

Arslan Ali 29.08.2024 20:57

@SchwernI Я вставил в свой вопрос неправильный пример. Я сейчас вставил правильный. Я тестирую контроллер и сравниваю проанализированный ответ конечной точки с хешем.

Arslan Ali 29.08.2024 21:18

@ArslanAli Спасибо, я посмотрю еще раз.

Schwern 29.08.2024 21:41

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

Schwern 29.08.2024 21:58

Проблема не в Rails и RSpec. Проблема в том, что to_json с радостью преобразует экземпляр Time в его представление ISO 8601, но JSON.parse не сделает обратного — он просто сохранит строку ISO 8601.

Stefan 30.08.2024 14:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
58
3
Перейти к ответу Данный вопрос помечен как решенный

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

Другие вопросы по теме