Estou usando Ruby on Rails 4 e rspec-rails gem 2.14. Para um meu objeto, gostaria de comparar a hora atual com o updated_at
atributo do objeto após a execução de uma ação do controlador, mas estou com problemas porque a especificação não passa. Ou seja, dado o seguinte está o código de especificação:
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at).to eq(Time.now)
end
Quando executo a especificação acima, recebo o seguinte erro:
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 ==)
Como posso fazer a especificação passar?
Observação : tentei também o seguinte (observe a utc
adição):
it "updates updated_at attribute" do
Timecop.freeze
patch :update
@article.reload
expect(@article.updated_at.utc).to eq(Time.now)
end
mas a especificação ainda não passa (observe a diferença de valor "obtido"):
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
fonte
fonte
===
, mas isso pode sofrer ao cruzar limites secundários. Provavelmente, o melhor é encontrar ou escrever seu próprio matcher, no qual você converte para segundos de época e permite uma pequena diferença absoluta.===
vez de==
- atualmente você está comparando o object_id de dois objetos Time diferentes. Embora o Timecop não congele o tempo do servidor do banco de dados. . . então, se seus carimbos de data / hora estiverem sendo gerados pelo RDBMS, não funcionaria (espero que isso não seja um problema para você aqui)Respostas:
O objeto Ruby Time mantém uma precisão maior do que o banco de dados. Quando o valor é lido de volta no banco de dados, ele é preservado apenas na precisão de microssegundos, enquanto a representação na memória é precisa em nanossegundos.
Se você não se importa com a diferença de milissegundos, você poderia fazer um to_s / to_i em ambos os lados da sua expectativa
ou
Consulte isso para obter mais informações sobre por que os horários são diferentes
fonte
Timecop
gema. Deveria apenas resolver o problema "congelando" o tempo?be_within
é a certaexpect {FooJob.perform_now}.to have_enqueued_job(FooJob).at(some_time)
Não tenho certeza se o.at
matcher aceitaria um tempo convertido em inteiro com.to_i
alguém que enfrentou esse problema?Acho que usar o
be_within
matcher rspec padrão é mais elegante:fonte
to_s
. Além disso,to_i
eto_s
pode falhar com pouca frequência se o tempo estiver próximo ao final de um segundo.be_within
matcher foi adicionado ao RSpec 2.1.0 em 7 de novembro de 2010 e aprimorado algumas vezes desde então. rspec.info/documentation/2.14/rspec-expectations/…undefined method 'second' for 1:Fixnum
. Existe algo que eu precisorequire
?.second
é uma extensão do rails: api.rubyonrails.org/classes/Numeric.htmlSim, pois
Oin
está sugerindo que obe_within
matcher é a melhor prática... e tem mais alguns uscases -> http://www.eq8.eu/blogs/27-rspec-be_within-matcher
Mas mais uma maneira de lidar com isso é usar atributos
midday
e embutidos do Railsmiddnight
.Agora, isso é apenas para demonstração!
Eu não usaria isso em um controlador, pois você está copiando todas as chamadas Time.new => todos os atributos de tempo terão o mesmo tempo => pode não provar o conceito que você está tentando alcançar. Eu geralmente o uso em objetos Ruby compostos semelhantes a este:
Mas, honestamente, eu recomendo ficar com o
be_within
matcher, mesmo quando estiver comparando Time.now.midday!Então, sim, por
be_within
favor, use o combinador;)atualização 2017-02
Pergunta no comentário:
você pode passar qualquer matcher RSpec para o
match
matcher (então, por exemplo, você pode até mesmo fazer testes de API com RSpec puro )Quanto a "tempos pós-db", acho que você quer dizer string que é gerada após salvar no banco de dados. Eu sugeriria desacoplar este caso de 2 expectativas (uma garantindo a estrutura hash, a segunda verificando o tempo) para que você possa fazer algo como:
Mas se este caso for muito frequente em seu conjunto de testes, eu sugeriria escrever seu próprio matcher RSpec (por exemplo
be_near_time_now_db_string
) convertendo o tempo da string db para o objeto Time e então usar isso como parte dematch(hash)
:fonte
expect(hash_1).to eq(hash_2)
funcionar quando alguns valores hash_1 são pré-tempos db e os valores correspondentes em hash_2 são tempos pós-db?Postagem antiga, mas espero que ajude quem entrar aqui por uma solução. Acho que é mais fácil e confiável apenas criar a data manualmente:
Isso garante que a data armazenada seja a correta, sem fazer
to_x
ou se preocupar com decimais.fonte
Você pode converter o objeto de data / data / hora / hora em uma string conforme é armazenado no banco de dados com
to_s(:db)
.fonte
A maneira mais fácil que encontrei de contornar esse problema é criar um
current_time
método auxiliar de teste assim:Agora, o tempo é sempre arredondado para o milissegundo mais próximo para que as comparações sejam diretas:
fonte
.change(usec: 0)
é muito útil.change(usec: 0)
truque para resolver o problema sem usar um auxiliar de especificação também. Se a primeira linha for,Timecop.freeze(Time.current.change(usec: 0))
então podemos simplesmente comparar.to eq(Time.now)
no final.Como eu estava comparando hashes, a maioria dessas soluções não funcionou para mim, então descobri que a solução mais fácil era simplesmente pegar os dados do hash que estava comparando. Uma vez que os updates_at times não são realmente úteis para eu testar, isso funciona bem.
fonte