The Bang Is for Surprise
Note: I’m using Ruby 2.0.0 and RSpec 2.13.1 for these samples. Behavior may be slightly different in older versions. YMMV!
One of the more well known features of RSpec is
let
.
It provides a way to create a variable as a
bareword which is lazy loaded
and memoized.
It also has a sibling let!
. On the surface, let!
is just a let
without
the lazy loading. So any variable defined with a let!
will always be created
before a test. This tool has a few nuances that should be know before you reach
for it.
Take the following sample. What do you think the result will be?
1 2 3 4 5 6 7 8 9 10 11 |
|
The test passes. This may or may not have been surprising. The rules for nested
let
s, is that the let
defined closet to the test in the hierarchy wins.
But, didn’t I say let!
always created the object which was then memoized?
I’ll get to more about how let
and let!
are implemented in a minute. For
now, I want to point out a subtle surprise waiting for you; or possibly bring
to light that nagging itch at the back of your brain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Surprise! The test fails:
1 2 3 4 5 6 7 |
|
user
was never explicitly referenced in our test or a before
block. Above I
also stated that the let
closest to the test wins. Theoretically, by these
rules one would naturally think the test would have passed. Yet, someone was
created in the database.
Which user definition do you think was created?
If we dump the User
collection before the expect
line we see:
1
|
|
Not only did the inner normal let
block appear to override the outer, the
outer let!
behavior took affect!
Let’s try one more:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Surprise! It passes:
1 2 3 4 5 6 |
|
Again, dumping the user created we see our good friend Alice:
1
|
|
If you’re scratching your brain right now. Don’t worry. I did too the first time. However, once we cover what is happening behind that curtain things will make perfect sense.
How let
and let!
Work
“Pay [no] attention to that man behind the curtain!”
- The Wizard
The reason for this behavior, remember I’m using RSpec 2.13, is that let!
just calls let
and before
behind the scenes:
1 2 3 4 |
|
And all
let
does is setup a memoized method based on the name and provided block:
1 2 3 4 5 |
|
“Using Conflicting Let and Let!” Explained
Going back to the example “Using Conflicting Let and Let!” above, where both
let!
and let
were used. It should be a bit clearer what is really going on.
When the test runs, the let!
has already created the before
block, which
will send the message :user
. However, the inner context’s let
created a new
method with the same name. Thus based on standard Ruby method lookup, when the
before
block runs the inner method receives the message:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
“Using Conflicting Nested Let!” Explained
It should also start to make sense what was going on with the “Using Conflicting Nested Let!” example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
It’s Just a Method
I hope that helps demystify the behavior.
Since let
is just a helper for setting up methods on the example group object
you can call super
in it; though this is generally not an advised practice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 |
|
Avoiding Ambiguity
Since that tiny little !
can be hard to see, especially in a sea of let
declarations, it is easy to miss it and get surprised. Additionally, seeing as
how mixing let!
and let
can lead to some surprises, it’s fairly clear why
let!
has started, rightly so in my opinion, to fall out of favor with some of
the RSpec crowd.
Luckily, there should be very few situations you should find yourself in where
you want to reach for let!
over let
. For me, this is usually a situation
where I’m creating some sort of persisted resource. For instance, the factory
example, or creating a test file on the system.
Options For Preloading
If people are moving away from using let!
, how should you preload variables?
Reference in before
Call them just like RSpec does in a before
:
1 2 3 4 5 6 7 |
|
To me this looks a bit odd. People I’ve talked to tend to have two reactions:
- Why are you referencing a ‘variable’ and not using it?
- Wouldn’t it be a bit more explicit to show the creation using an
@var
?
By now you should know that the first response indicates a lack of understand on how RSpec works. They aren’t variables, they are actually bareword messages.
The second response is a valid point. The result would be:
1 2 3 4 |
|
This goes back to preference and style. My preference is to reach for a
bareword whenever I can. One reason is that, when using an instance variables
you are now locked in to how both @bob
and @alice
are created. If you later
wanted to modify them, you could but at the expense of already having created
the persisted resource; remember before
blocks execute outside-in (this isn’t
so much of an issue for lightweight objects). Or you have to roll your own
memoization scheme (not hard just duplication of work).
Use a method
The next common thing I see done is people say: “I’ll just wrap it all up in a method.”
1 2 3 4 5 6 |
|
Now the before
looks better; it’s explicit what is happening. However, the
new create_users
method looks just like our old before
. So this really just
added one level of indirection. The main advantage here is if we need to
change the behavior we can just write a new create_users
method in an inner
context. We could also use barewords by making our variables into methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Though now we’ve duplicated the lazy loading and memoizing logic already
provided by let
.
At this point, you’ll probably say, we can make this a bit more explicit and clean it up at the same time:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This brings me to my next option.
Explicit Preload
Now there’s nothing inherently wrong with the above methods. However, to me they add a lot of work, without adding much additional value. There are still cases where I’ll break out the generator method as it’s a very useful tool. But this section is about another option, so I’ll get to it.
Having gone through the cycle of improvement the “hard” way, it’s time to show you the shortcut. To me, this is reminiscent of high school calculus class where the teacher made me do everything the difficult, time consuming way, for a week before teaching how it’s usually done with the shorter method.
Since pretty much everything in RSpec is already just a method, we can leverage that to get our desired behavior. This was discussed in a pull request:
1 2 3 4 5 6 7 8 9 10 11 |
|
You can place the module code anywhere you want (usually in spec/support
).
Then you’ll load it in a RSpec.configure
block either in the same file or in
spec_helper.rb
.
Our setup now looks like:
1 2 3 4 |
|
Going back to our original example. There is now more context to what is
happening without the confusing mix of let
and let!
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Introducing Conjurer
Gem
I’ve started using this in enough new projects that I wanted an easy way to
just add it. I also wanted to be able to include any changes easily. Thus, I’ve
rolled it all up into a gem: conjurer
Happy RSpecing!!