Rails Время несоответствия с rspec
Я работаю с Time in Rails и используя следующий код для настройки даты начала и окончания проекта:
start_date ||= Time.now
end_date = start_date + goal_months.months
Затем я клонирую объект, и я пишу rspec тесты, чтобы подтвердить соответствие атрибутов в копии. Соответствие даты окончания:
original[end_date]: 2011-08-24 18:24:53 UTC
clone[end_date]: 2011-08-24 18:24:53 UTC
Забастовкa >
но спецификация дает мне ошибку в датах начала:
expected: Wed Aug 24 18:24:53 UTC 2011,
got: Wed, 24 Aug 2011 18:24:53 UTC +00:00 (using ==)
Очистить даты одинаковые, только отформатированные по-разному. Как получается, что они в конечном итоге хранятся по-разному в базе данных и как мне их сопоставить? Я также пробовал его с DateTime с теми же результатами.
Исправление: Даты окончания не совпадают. Они распечатывают то же самое, но ошибки rspec также на них. Когда я распечатываю дату начала и дату окончания, значения выходят в разных форматах:
start date: 2010-08-24T19:00:24+00:00
end date: 2011-08-24 19:00:24 UTC
Ответы
Ответ 1
Вы должны высмеять теперь метод Time, чтобы он всегда соответствовал дате в спецификации. Вы никогда не знаете, когда задержка заставит spec сбой из-за нескольких миллисекунд. Этот подход также гарантирует, что время на реальном коде и на спецификации совпадают.
Если вы используете стандартную rspec mock lib, попробуйте сделать что-то вроде:
t = Time.parse("01/01/2010 10:00")
Time.should_receive(:now).and_return(t)
Ответ 2
Это обычно происходит, потому что rspec пытается сопоставить разные объекты: Time и DateTime, например. Кроме того, сопоставимые времена могут немного отличаться на несколько миллисекунд.
Во втором случае правильным способом является использование stubbing и mock. Также см. драгоценный камень TimeCop
В первом случае возможным решением может быть сравнение временных меток:
actual_time.to_i.should == expected_time.to_i
Я использую простой matcher для таких случаев:
# ./spec/spec_helper.rb
#
# Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
#
#
# Usage:
#
# its(:updated_at) { should be_the_same_time_as updated_at }
#
#
# Will pass or fail with message like:
#
# Failure/Error: its(:updated_at) { should be_the_same_time_as 2.days.ago }
# expected Tue, 07 Jun 2011 16:14:09 +0300 to be the same time as Mon, 06 Jun 2011 13:14:09 UTC +00:00
RSpec::Matchers.define :be_the_same_time_as do |expected|
match do |actual|
expected.to_i == actual.to_i
end
end
Ответ 3
Я полностью согласен с предыдущими ответами о stubbing Time.now. Тем не менее, здесь есть еще одна вещь. Когда вы сравниваете данные из базы данных, вы теряете часть фракционного времени, которое может быть в ruby DateTime obj. Лучший способ сравнить дату таким образом в Rspec:
database_start_date.should be_within(1).of(start_date)
Ответ 4
Один из способов заключается в том, что объекты Ruby Time имеют наносекундную точность, но большинство баз данных имеют максимальную точность в микросекундах. Лучший способ обойти это - это заглушить Time.now(или использовать timecop) с круглым числом. Прочитайте сообщение, которое я написал об этом: http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/
Ответ 5
Мое первоначальное предположение заключалось в том, что значение Time.now отформатировано иначе, чем значение вашей базы данных.
Ответ 6
Вы уверены, что используете ==
, а не eql
или be
? Последние два метода используют идентификатор объекта, а не сравнение значений.
Из вывода видно, что ожидаемое значение равно Time
, а тестируемое значение - DateTime
. Это также может быть проблемой, хотя я смущаюсь угадать, как исправить это, учитывая почти патологический характер библиотек даты и времени Ruby...
Ответ 7
Одно из решений, которое мне нравится, - просто добавить следующее к spec_helper
:
class Time
def ==(time)
self.to_i == time.to_i
end
end
Таким образом, он полностью прозрачен даже во вложенных объектах.
Ответ 8
Добавление .to_datetime
к обеим переменным приведет к эквиваленту эквивалентных значений даты и времени и соблюдению временных зон и летнего времени. Для сравнения только даты используйте .to_date
.
Пример спецификации с двумя переменными:
actual_time.to_datetime.should == expected_time.to_datetime
Лучшая спецификация с ясностью:
actual_time.to_datetime.should eq 1.month.from_now.to_datetime
.to_i
вызывает неоднозначность в отношении этого значения в спецификациях.
+1 для использования драгоценного камня TimeCop в спецификациях. Просто убедитесь, что вы проверили летнее время в ваших спецификациях, если на ваше приложение влияет DST.
Ответ 9
Нашим текущим решением является метод freeze_time
, который обрабатывает округление:
def freeze_time(time = Time.zone.now)
# round time to get rid of nanosecond discrepancies between ruby time and
# postgres time
time = time.round
Timecop.freeze(time) { yield(time) }
end
И тогда вы можете использовать его как:
freeze_time do
perform_work
expect(message.reload.sent_date).to eq(Time.now)
end
Ответ 10
В зависимости от ваших спецификаций вы можете использовать Rails-native путевые помощники:
# in spec_helper.rb
config.include ActiveSupport::Testing::TimeHelpers
start_date ||= Time.current.change(usecs: 0)
end_date = start_date + goal_months.months
travel_to start_date do
# Clone here
end
expect(clone.start_date).to eq(start_date)
Без Time.current.change(usecs: 0)
он может жаловаться на разницу между часовыми поясами. Или между микросекундами, поскольку хелпер будет reset переданное значение внутренне (Timecop имеет аналогичную проблему, поэтому reset usecs
с ним тоже).