web-dev-qa-db-fra.com

Difficulté à comparer le temps avec RSpec

J'utilise Ruby sur Rails 4 et le gem Rspec-Rails 2.14. Pour un objet, je voudrais comparer l'heure actuelle avec le updated_at attribut object après une action du contrôleur, mais je suis en difficulté car la spécification ne passe pas. C'est-à-dire, étant donné que voici le code de spécification:

it "updates updated_at attribute" do
  Timecop.freeze

  patch :update
  @article.reload
  expect(@article.updated_at).to eq(Time.now)
end

Lorsque j'exécute les spécifications ci-dessus, l'erreur suivante apparaît:

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 ==)

Comment puis-je faire la spécification pour passer?


Note: J'ai aussi essayé ce qui suit (notez l'addition utc):

it "updates updated_at attribute" do
  Timecop.freeze

  patch :update
  @article.reload
  expect(@article.updated_at.utc).to eq(Time.now)
end

mais la spécification ne passe toujours pas (notez la différence de valeur "got"):

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 ==)
110
Backo

L'objet Ruby Time conserve une précision supérieure à celle de la base de données. Lorsque la valeur est lue dans la base de données, elle n’est conservée qu’à une précision de la microseconde, tandis que la représentation en mémoire est précise à la nanoseconde.

Si vous ne vous souciez pas de la différence en millisecondes, vous pouvez faire un to_s/to_i des deux côtés de vos attentes.

expect(@article.updated_at.utc.to_s).to eq(Time.now.to_s)

ou

expect(@article.updated_at.utc.to_i).to eq(Time.now.to_i)

Reportez-vous à this pour plus d'informations sur les raisons pour lesquelles les temps sont différents

142
usha

Je trouve en utilisant le be_within défaut rspec Matcher plus élégant:

expect(@article.updated_at.utc).to be_within(1.second).of Time.now
179
Oin

Ancien post, mais j'espère que cela aidera toute personne qui entre ici pour trouver une solution. Je pense qu'il est plus facile et plus fiable de simplement créer la date manuellement:

it "updates updated_at attribute" do
  freezed_time = Time.utc(2015, 1, 1, 12, 0, 0) #Put here any time you want
  Timecop.freeze(freezed_time)

  patch :update
  @article.reload
  expect(@article.updated_at).to eq(freezed_time)
end

Cela garantit que la date mémorisée est la bonne, sans faire to_x Ni se soucier des décimales.

8
jBilbo

yep as Oin suggère que matcher be_within est la meilleure pratique

... et il a encore quelques cas d'utilisation -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher

Mais une autre façon de gérer cela consiste à utiliser les attributs Rails intégrés à midday et middnight.

it do
  # ...
  stubtime = Time.now.midday
  expect(Time).to receive(:now).and_return(stubtime)

  patch :update 
  expect(@article.reload.updated_at).to eq(stubtime)
  # ...
end

Maintenant c'est juste pour la démonstration!

Je ne voudrais pas utiliser ceci dans un contrôleur car vous écrasez tous les Time.new call => tous les attributs de temps auront le même temps => ne prouvent peut-être pas le concept que vous essayez d'atteindre. Je l'utilise habituellement dans des objets composés Ruby).

class MyService
  attr_reader :time_evaluator, resource

  def initialize(resource:, time_evaluator: ->{Time.now})
    @time_evaluator = time_evaluator
    @resource = resource
  end

  def call
    # do some complex logic
    resource.published_at = time_evaluator.call
  end
end

require 'rspec'
require 'active_support/time'
require 'ostruct'

RSpec.describe MyService do
  let(:service) { described_class.new(resource: resource, time_evaluator: -> { Time.now.midday } ) }
  let(:resource) { OpenStruct.new }

  it do
    service.call
    expect(resource.published_at).to eq(Time.now.midday)    
  end
end

Mais honnêtement, je recommande de rester avec be_within Matcher même en comparant Time.now.midday!

Donc, oui, veuillez vous en tenir à be_within Matcher;)


mise à jour 2017-02

Question en commentaire:

et si les temps sont dans un hachage? n'importe quel moyen de rendre attendu (hash_1) .to eq (hash_2) fonctionne lorsque certaines valeurs de hash_1 correspondent à des durées antérieures à la base de données et que les valeurs correspondantes dans hash_2 correspondent à des durées postérieures à la base de données? -

expect({mytime: Time.now}).to match({mytime: be_within(3.seconds).of(Time.now)}) `

vous pouvez passer n'importe quel correcteur RSpec à correcteur match (par exemple, vous pouvez même le faire test de l'API avec RSpec pur )

En ce qui concerne "post-db-times", je suppose que vous entendez une chaîne générée après une sauvegarde dans une base de données. Je suggérerais de découpler ce cas à 2 attentes (une assurant la structure de hachage, une seconde vérifiant l'heure) afin que vous puissiez faire quelque chose comme:

hash = {mytime: Time.now.to_s(:db)}
expect(hash).to match({mytime: be_kind_of(String))
expect(Time.parse(hash.fetch(:mytime))).to be_within(3.seconds).of(Time.now)

Mais si ce cas est trop souvent dans votre suite de tests, je vous suggèrerais d'écrire votre propre RSpec matcher (par exemple be_near_time_now_db_string) En convertissant la chaîne db time en objet Time, puis utilisez-le dans le cadre de le match(hash):

 expect(hash).to match({mytime: be_near_time_now_db_string})  # you need to write your own matcher for this to work.
8
equivalent8

Vous pouvez convertir l'objet date/date/heure/heure en une chaîne telle qu'elle est stockée dans la base de données avec to_s(:db).

expect(@article.updated_at.to_s(:db)).to eq '2015-01-01 00:00:00'
expect(@article.updated_at.to_s(:db)).to eq Time.current.to_s(:db)
7
Thomas Klemm

Le moyen le plus simple que j'ai trouvé autour de ce problème est de créer un current_time tester la méthode d'assistance comme suit:

module SpecHelpers
  # Database time rounds to the nearest millisecond, so for comparison its
  # easiest to use this method instead
  def current_time
    Time.zone.now.change(usec: 0)
  end
end

RSpec.configure do |config|
  config.include SpecHelpers
end

Maintenant, le temps est toujours arrondi à la milliseconde près pour que les comparaisons soient simples:

it "updates updated_at attribute" do
  Timecop.freeze(current_time)

  patch :update
  @article.reload
  expect(@article.updated_at).to eq(current_time)
end
6
Sam Davies