Rails Time несоответствия с rspec - PullRequest
22 голосов
/ 24 августа 2010

Я работаю с 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

но спецификация выдает мне ошибку в даты начала:

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

Ответы [ 10 ]

50 голосов
/ 08 июня 2011

Обычно это происходит потому, что rspec пытается сопоставить разные объекты: например, Time и DateTime.Кроме того, сравнимое время может немного отличаться на несколько миллисекунд.

Во втором случае правильный способ - использовать заглушки и макеты.Также см. TimeCop gem

. В первом случае возможное решение может состоять в сравнении временных меток:

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
23 голосов
/ 24 августа 2010

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

Если вы используете rspec mock lib по умолчанию, попробуйте сделать что-то вроде:

t = Time.parse("01/01/2010 10:00")
Time.should_receive(:now).and_return(t)
20 голосов
/ 07 августа 2012

Я полностью согласен с предыдущими ответами о заглушке Time.now.Тем не менее, здесь происходит еще одна вещь.Когда вы сравниваете datetime из базы данных, вы теряете часть времени фракции, которое может быть в ruby ​​DateTime obj.Лучший способ сравнить дату таким образом в Rspec:

database_start_date.should be_within(1).of(start_date)
6 голосов
/ 08 августа 2011

Одно замечание: объекты Ruby Time обладают точностью до наносекунды, но большинство баз данных имеют точность не более микросекунды. Лучший способ обойти это - заглушить Time.now (или использовать timecop) с круглым числом. Прочитайте пост, который я написал об этом: http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/

0 голосов
/ 13 сентября 2017

В зависимости от ваших спецификаций вы можете использовать Rails-native travel helpers :

# 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), скорее всего, будут жаловаться на разницу между часовыми поясами. Или между микросекундами, поскольку помощник внутренне сбросит переданное значение (у Timecop есть похожая проблема, поэтому сбросьте также usecs с ним).

0 голосов
/ 02 марта 2015

Наше текущее решение - использовать метод 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
0 голосов
/ 20 ноября 2013

Добавление .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 gem в спецификациях. Просто проверьте летнее время в своих спецификациях, если на ваше приложение влияет DST.

0 голосов
/ 15 октября 2013

Одно из решений, которое мне нравится, это просто добавить следующее к spec_helper:

class Time
  def ==(time)
    self.to_i == time.to_i
  end
end

Таким образом, он полностью прозрачен даже во вложенных объектах.

0 голосов
/ 24 августа 2010

Вы уверены, что используете ==, а не eql или be?Последние два метода используют идентичность объекта, а не сравнивают значения.

Из полученных результатов видно, что ожидаемое значение равно Time, а тестируемое значение - DateTime.Это также может быть проблемой, хотя я не решусь догадаться, как это исправить, учитывая почти патологический характер библиотек даты и времени Руби ...

0 голосов
/ 24 августа 2010

Моим первоначальным предположением будет то, что значение Time.now отформатировано иначе, чем значение вашей базы данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...