An interesting question came up today in a code review around using let and let! in Rspec in ruby. When do you use which and why?

Its answered briefly and very concisely here as this:

Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.

Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.

but maybe it's a little too concise and needs a bit of unfolding...

Firstly. a few facts:

  1. both let and let! variables are reset between example runs
  2. Within the same example. both let and let! re-use and thus cache the value of the defining code block that makes up the definition (intra-example)
  3. only let! will re-evaluate its value immediately before a new example, while a let will wait until it's first encountered in the example, before re-evaluating/re-determining its value.

With a let! variable you're basically saying that before it's ever first even used in any example, first explicitly run its defining block before the start of the example ie evaluate its value. Then, this resulting value will be used as its value within the example (and it will remain unchanged within the example, ie it will be cached intra-example)

Otherwise, with let, as soon as the let variable is encountered for the first time in the example(it's lazy-evaluated), only then will it be evaluated, and then re-used, without re-evaluation(much like let!) ie cached throughout the example.

An example for let! being useful would be if during the execution of an example(before the let variable is evaluated because it's not used yet), the example code goes and invalidates data that the let variable ordinarily depends on. Later on, when the let variables is encountered for the first time, it is then evaluated and uses now invalid data it depended on). This now sets the let variable to data that would not have existed in this way prior to having the example started/run.

Had the let variable been evaluated before the example had gone ahead and invalided its dependent data, ie it was pre-evaluated before the example (ie if it was declared with let! instead), it would not pick up the new(erroneous?) changes to the dependant data.

This is particularly true if the dependant data is a shared database modified throughout the example and then which affects the fist-time evaluation(lazy-evaluated) of a let variable:

Valid instances of let! might be:

let!(:country) { Country.where(code: 'US').first }

where the contents of the database's Country has changes made to it throughout the example, but you want the first country to be the first country at the time the example started, not when the :country variable is first evaluated i.e when the example gets to it in the code (as it might have changed since then in the database).

Generally speaking, you should use let when if the variable does not depend on any data that will change during the lifetime of the example:

let!(:customer) { FactoryGirl.create(:customer) }

Otherwise using let by default is usually enough