web-dev-qa-db-fra.com

Rails - RSpec - Différence entre "let" et "let!"

J'ai lu ce que le manuel RSpec dit sur la différence, mais certaines choses sont toujours confuses. Toutes les autres sources, y compris "The RSpec Book" n'expliquent que "let", et "The Rails 3 Way" est tout aussi déroutant que le manuel.

Je comprends que "let" n'est évalué que lorsqu'il est invoqué et conserve la même valeur dans une portée. Il est donc logique que dans le premier exemple du manuel le premier test réussit car le "let" n'est invoqué qu'une seule fois, et le second test réussit car il ajoute à la valeur du premier test (qui a été évalué une fois lors du premier test et a la valeur 1).

Suite à cela, puisque "laissez!" évalue une fois défini, et de nouveau lorsqu'il est invoqué, le test ne devrait-il pas échouer car "count.should eq (1)" aurait dû plutôt être "count.should eq (2)"?

Toute aide serait appréciée.

40
Theo Scholiadis

Elle n'est pas invoquée lorsqu'elle est définie, mais plutôt avant chaque exemple (puis elle est mémorisée et n'est plus invoquée par l'exemple). De cette façon, count aura une valeur de 1.

Quoi qu'il en soit, si vous avez un autre exemple, le hook avant est à nouveau invoqué - tous les tests suivants réussissent:

$count = 0
describe "let!" do
  invocation_order = []

  let!(:count) do
    invocation_order << :let!
    $count += 1
  end

  it "calls the helper method in a before hook" do
    invocation_order << :example
    invocation_order.should == [:let!, :example]
    count.should eq(1)
  end

  it "calls the helper method again" do
    count.should eq(2)
  end
end
14
dhoelzgen

J'ai compris la différence entre let et let! avec un exemple très simple. Permettez-moi de lire la phrase doc en premier, puis de montrer la sortie.

À propos de let doc dit: -

... let is paresseux: il n'est évalué que la première fois que la méthode qu'il définit est invoquée.

J'ai compris la différence avec l'exemple ci-dessous: -

$count = 0
describe "let" do
  let(:count) { $count += 1 }

  it "returns 1" do
    expect($count).to eq(1)
  end
end

Permet de l'exécuter maintenant: -

arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb
F

Failures:

  1) let is not cached across examples
     Failure/Error: expect($count).to eq(1)

       expected: 1
            got: 0

       (compared using ==)
     # ./spec/test_spec.rb:8:in `block (2 levels) in <top (required)>'

Finished in 0.00138 seconds (files took 0.13618 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/test_spec.rb:7 # let is not cached across examples
arup@linux-wzza:~/Ruby>

Pourquoi l'erreur [~ # ~] [~ # ~]? Parce que, comme le dit doc, avec let, il n'est évalué que la première fois que la méthode qu'il définit est invoquée. Dans le = exemple, nous n'avons pas appelé le count, donc $count est toujours 0, non incrémenté de 1.

Venons-en maintenant à la partie let!. Le doc dit

.... Vous pouvez utiliser let! pour forcer l'invocation de la méthode avant chaque exemple. Cela signifie que même si vous n'avez pas invoqué la méthode helper à l'intérieur de exemple, elle sera toujours invoquée avant l'exécution de votre exemple.

Testons cela aussi: -

Voici le code modifié

$count = 0
describe "let!" do
  let!(:count) { $count += 1 }

  it "returns 1" do
    expect($count).to eq(1)
  end
end

Permet d'exécuter ce code: -

arup@linux-wzza:~/Ruby> rspec spec/test_spec.rb
.

Finished in 0.00145 seconds (files took 0.13458 seconds to load)
1 example, 0 failures

Voyez, maintenant $count Retour 1, ainsi le test a été réussi. C'est arrivé quand j'ai utilisé let!, qui s'exécutait avant l'exemple, bien que nous n'ayons pas appelé count dans notre exemple.

Voici comment let et let! diffère les uns des autres.

48
Arup Rakshit

Vous pouvez en savoir plus à ce sujet ici , mais en gros. (:let) est évalué paresseusement et ne sera jamais instancié si vous ne l'appelez pas, tandis que (:let!) est évalué avec force avant chaque appel de méthode.

27
Justin Herrick

Je pensais aussi que c'était déroutant, mais je pense que les exemples de The Rails 3 Way sont bons.
let est analogue aux variables d'instance dans le bloc before alors que let! est mémorisé immédiatement

Depuis le Rails 3 Way

describe BlogPost do
  let(:blog_post) { BlogPost.create :title => 'Hello' }
  let!(:comment) { blog_post.comments.create :text => 'first post' }

  describe "#comment" do
    before do
     blog_post.comment("finally got a first post")
    end

    it "adds the comment" do
      blog_post.comments.count.should == 2
    end
  end
end

"Puisque le bloc de commentaire n'aurait jamais été exécuté pour la première assertion si vous avez utilisé une définition let, un seul commentaire aurait été ajouté dans cette spécification même si l'implémentation peut fonctionner. En utilisant let! Nous nous assurons que le commentaire initial est créé et la spécification va maintenant passer. "

5
cluv

J'ai également été confondu par let et let!, j'ai donc pris le code de documentation de ici et joué avec: https://Gist.github.com/3489451

J'espère que ça aide!

2
Jonathan Lin

Et voici un moyen de garder vos spécifications prévisibles.

Vous devriez presque toujours utiliser let. Vous ne devez pas utiliser let! sauf si vous souhaitez intentionnellement mettre en cache la valeur dans les exemples. C'est pourquoi:

describe '#method' do
  # this user persists in the db across all sub contexts
  let!(:user) { create :user }

  context 'scenario 1' do
    context 'sub scenario' do
      # ...
      # 1000 lines long
      # ...
    end

    context 'sub scenario' do
      # you need to test user with a certain trait
      # and you forgot someone else (or yourself) already has a user created
      # with `let!` all the way on the top
      let(:user) { create :user, :trait }

      it 'fails even though you think it should pass' do
        # this might not be the best example but I found this pattern
        # pretty common in different code bases
        # And your spec failed, and you scratch your head until you realize
        # there are more users in the db than you like
        # and you are just testing against a wrong user
        expect(User.first.trait).to eq xxx
      end
    end
  end
end
1
Edmund Lee