Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails - RSpec - Difference between "let" and "let!"

Tags:

ruby

rspec

rspec2

I have read what the RSpec manual says about the difference, but some things are still confusing. Every other source, including "The RSpec Book" only explain about "let", and "The Rails 3 Way" is just as confusing as the manual.

I understand that "let" is only evaluated when invoked, and keeps the same value within a scope. So it makes sense that in the first example in the manual the first test passes as the "let" is invoked only once, and the second test passes as it adds to the value of the first test (which was evaluated once in the first test and has the value of 1).

Following that, since "let!" evaluates when defined, and again when invoked, should the test not fail as "count.should eq(1)" should have instead be "count.should eq(2)"?

Any help would be appreciated.

like image 437
Theo Scholiadis Avatar asked Apr 16 '12 11:04

Theo Scholiadis


People also ask

What is the difference between let and let RSpec?

I understood the difference between let and let! with a very simple example. Let me read the doc sentence first, then show the output hands on. ... let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked.

What is let in RSpec?

let generates a method whose return value is memoized after the first call. This is known as lazy loading because the value is not loaded into memory until the method is called. Here is an example of how let is used within an RSpec test. let will generate a method called thing which returns a new instance of Thing .

How do I set an instance variable in RSpec?

If you want to set a controller or view's instance variables in your RSpec test, then call assign either in a before block or at the start of an example group. The first argument is the name of the instance variable while the second is the value you want to assign to it.

How do I mock an instance variable in RSpec?

You can't mock an instance variable. You can only mock methods. One option is to define a method inside OneClass that wraps the another_member , and mock that method. However, you don't have to, there is a better way to write and test your code.


2 Answers

I understood the difference between let and let! with a very simple example. Let me read the doc sentence first, then show the output hands on.

About let doc says :-

... let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked.

I understood the difference with the below example :-

$count = 0 describe "let" do   let(:count) { $count += 1 }    it "returns 1" do     expect($count).to eq(1)   end end 

Lets run it now :-

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> 

Why the ERROR ? Because, as doc said, with let, it is not evaluated until the first time the method it defines is invoked. In the example, we didn't invoke the count, thus $count is still 0, not incremented by 1.

Now coming to the part let!. The doc is saying

....You can use let! to force the method's invocation before each example. It means even if you didn't invoke the helper method inside the example, still it will be invoked before your example runs.

Lets test this also :-

Here is the modified code

$count = 0 describe "let!" do   let!(:count) { $count += 1 }    it "returns 1" do     expect($count).to eq(1)   end end 

Lets run this 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 

See, now $count returns 1, thus test got passed. It happened as I used let!, which run before the example run, although we didn't invoke count inside our example.

This is how let and let! differs from each other.

like image 140
Arup Rakshit Avatar answered Oct 10 '22 19:10

Arup Rakshit


You can read more about this here, but basically. (:let) is lazily evaluated and will never be instantiated if you don't call it, while (:let!) is forcefully evaluated before each method call.

like image 29
Justin Herrick Avatar answered Oct 10 '22 19:10

Justin Herrick