Я использую Ruby on Rails 4 и гем 2.14 rspec-rails. Для своего объекта я хотел бы сравнить текущее время с updated_at
атрибутом объекта после выполнения действия контроллера, но у меня проблемы, поскольку спецификация не проходит. То есть, учитывая следующий код спецификации:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
Когда я запускаю указанную выше спецификацию, я получаю следующую ошибку:
Failure/Error: expect(@article.updated_at).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: Thu, 05 Dec 2013 08:42:20 CST -06:00
(compared using ==)
Как я могу добиться соответствия спецификации?
Примечание : я пробовал также следующее (обратите внимание на utc
добавление):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
но спецификация по-прежнему не проходит (обратите внимание на разницу в значениях "получено"):
Failure/Error: expect(@article.updated_at.utc).to eq(Time.now)
expected: 2013-12-05 14:42:20 UTC
got: 2013-12-05 14:42:20 UTC
(compared using ==)
ruby-on-rails
ruby
time
rspec
comparison
Backo
источник
источник
===
, но это может пострадать от пересечения вторых границ. Вероятно, лучше всего найти или написать свой собственный сопоставитель, в котором вы конвертируете в секунды эпохи и допускаете небольшую абсолютную разницу.===
вместо==
- в настоящее время вы сравниваете object_id двух разных объектов Time. Хотя Timecop не заморозит время сервера базы данных. , , поэтому, если ваши временные метки генерируются РСУБД, это не сработает (хотя я думаю, что это не проблема для вас)Ответы:
Объект Ruby Time поддерживает большую точность, чем база данных. Когда значение считывается из базы данных, оно сохраняется только с точностью до микросекунд, тогда как представление в памяти с точностью до наносекунд.
Если вас не волнует разница в миллисекундах, вы можете использовать to_s / to_i с обеих сторон вашего ожидания.
или
Обратитесь к этому для получения дополнительной информации о том, почему времена разные.
источник
Timecop
драгоценный камень. Следует ли просто решить проблему, «заморозив» время?be_within
верноеexpect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time)
Я не уверен, что.at
сопоставитель примет время, преобразованное в целое число, если.to_i
кто-либо столкнулся с этой проблемой?Я считаю использование
be_within
сопоставления rspec по умолчанию более элегантным:источник
to_s
. Кроме того,to_i
иto_s
может нечасто выходить из строя, если время приближается к концу секунды.be_within
сопоставление было добавлено в RSpec 2.1.0 7 ноября 2010 г. и с тех пор несколько раз улучшалось. rspec.info/documentation/2.14/rspec-expectations/…undefined method 'second' for 1:Fixnum
. Мне что-то нужноrequire
?.second
- это расширение rails: api.rubyonrails.org/classes/Numeric.htmlСтарый пост, но я надеюсь, что он поможет любому, кто войдет сюда за решением. Думаю, проще и надежнее просто создать дату вручную:
Это гарантирует, что сохраненная дата является правильной, не
to_x
беспокоясь о десятичных дробях.источник
да, поскольку
Oin
предлагаетbe_within
сопоставление - лучшая практика... и еще несколько вариантов использования -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
Но еще один способ справиться с этим - использовать встроенные атрибуты
midday
иmiddnight
атрибуты Rails .Теперь это просто для демонстрации!
Я бы не стал использовать это в контроллере, поскольку вы заглушаете все вызовы Time. Новые вызовы => все временные атрибуты будут иметь одинаковое время => может не подтвердить концепцию, которую вы пытаетесь достичь. Я обычно использую его в составленных объектах Ruby, подобных этому:
Но, честно говоря, я рекомендую придерживаться
be_within
сопоставления даже при сравнении Time.now.midday!Так что да, пожалуйста, используйте
be_within
совпадение;)обновление 2017-02
Вопрос в комментарии:
вы можете передать любой сопоставитель RSpec сопоставителю
match
(например, вы даже можете проводить тестирование API с чистым RSpec )Что касается "post-db-times", я думаю, вы имеете в виду строку, которая генерируется после сохранения в БД. Я бы предложил разделить этот случай с двумя ожиданиями (одно обеспечивает хэш-структуру, второе проверяет время), поэтому вы можете сделать что-то вроде:
Но если этот случай слишком часто встречается в вашем наборе тестов, я бы предложил написать свой собственный сопоставитель RSpec (например
be_near_time_now_db_string
), преобразующий время строки db в объект Time, а затем использовать его как частьmatch(hash)
:источник
expect(hash_1).to eq(hash_2)
работать, когда некоторые значения hash_1 являются временами до db, а соответствующие значения в hash_2 - значениями после db?Вы можете преобразовать объект date / datetime / time в строку, поскольку она хранится в базе данных с помощью
to_s(:db)
.источник
Самый простой способ обойти эту проблему - создать
current_time
вспомогательный тестовый метод, например:Теперь время всегда округляется до ближайшей миллисекунды, чтобы сравнение было простым:
источник
.change(usec: 0)
очень помогает.change(usec: 0)
трюк для решения проблемы, не прибегая к помощи специального помощника. Если первая строка,Timecop.freeze(Time.current.change(usec: 0))
то мы можем просто сравнить.to eq(Time.now)
в конце.Поскольку я сравнивал хеши, большинство этих решений у меня не работали, поэтому я обнаружил, что самым простым решением было просто получить данные из сравниваемого хеша. Поскольку время updated_at на самом деле мне не подходит для тестирования, это работает нормально.
источник