When to use RSpec let()?
I always prefer let
to an instance variable for a couple of reasons:
- Instance variables spring into existence when referenced. This means that if you fat finger the spelling of the instance variable, a new one will be created and initialized to
nil
, which can lead to subtle bugs and false positives. Sincelet
creates a method, you'll get aNameError
when you misspell it, which I find preferable. It makes it easier to refactor specs, too. - A
before(:each)
hook will run before each example, even if the example doesn't use any of the instance variables defined in the hook. This isn't usually a big deal, but if the setup of the instance variable takes a long time, then you're wasting cycles. For the method defined bylet
, the initialization code only runs if the example calls it. - You can refactor from a local variable in an example directly into a let without changing the
referencing syntax in the example. If you refactor to an instance variable, you have to change
how you reference the object in the example (e.g. add an@
). - This is a bit subjective, but as Mike Lewis pointed out, I think it makes the spec easier to read. I like the organization of defining all my dependent objects with
let
and keeping myit
block nice and short.
A related link can be found here: http://www.betterspecs.org/#let
What's the difference between RSpec's subject and let? When should they be used or not?
Summary: RSpec's subject is a special variable that refers to the object being tested. Expectations can be set on it implicitly, which supports one-line examples. It is clear to the reader in some idiomatic cases, but is otherwise hard to understand and should be avoided. RSpec's let
variables are just lazily instantiated (memoized) variables. They aren't as hard to follow as the subject, but can still lead to tangled tests so should be used with discretion.
The subject
How it works
The subject is the object being tested. RSpec has an explicit idea of the subject. It may or may not be defined. If it is, RSpec can call methods on it without referring to it explicitly.
By default, if the first argument to an outermost example group (describe
or context
block) is a class, RSpec creates an instance of that class and assigns it to the subject. For example, the following passes:
class A
end
describe A do
it "is instantiated by RSpec" do
expect(subject).to be_an(A)
end
end
You can define the subject yourself with subject
:
describe "anonymous subject" do
subject { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end
You can give the subject a name when you define it:
describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(a).to be_an(A)
end
end
Even if you name the subject, you can still refer to it anonymously:
describe "named subject" do
subject(:a) { A.new }
it "has been instantiated" do
expect(subject).to be_an(A)
end
end
You can define more than one named subject. The most recently defined named subject is the anonymous subject
.
However the subject is defined,
It's instantiated lazily. That is, the implicit instantiation of the described class or the execution of the block passed to
subject
doesn't happen untilsubject
or the named subject is referred to in an example. If you want your explict subject to be instantiated eagerly (before an example in its group runs), saysubject!
instead ofsubject
.Expectations can be set on it implicitly (without writing
subject
or the name of a named subject):describe A do
it { is_expected.to be_an(A) }
endThe subject exists to support this one-line syntax.
When to use it
An implicit subject
(inferred from the example group) is hard to understand because
- It's instantiated behind the scenes.
- Whether it's used implicitly (by calling
is_expected
without an explicit receiver) or explicitly (assubject
), it gives the reader no information about the role or nature of the object on which the expectation is being called. - The one-liner example syntax doesn't have an example description (the string argument to
it
in the normal example syntax), so the only information the reader has about the purpose of the example is the expectation itself.
Therefore, it's only helpful to use an implicit subject when the context is likely to be well understood by all readers and there is really no need for an example description. The canonical case is testing ActiveRecord validations with shoulda matchers:
describe Article do
it { is_expected.to validate_presence_of(:title) }
end
An explict anonymous subject
(defined with subject
without a name) is a little better, because the reader can see how it's instantiated, but
- it can still put the instantiation of the subject far from where it's used (e.g. at the top of an example group with many examples that use it), which is still hard to follow, and
- it has the other problems that the implicit subject does.
A named subject provides an intention-revealing name, but the only reason to use a named subject instead of a let
variable is if you want to use the anonymous subject some of the time, and we just explained why the anonymous subject is hard to understand.
So, legitimate uses of an explicit anonymous subject
or a named subject are very rare.
let
variables
How they work
let
variables are just like named subjects except for two differences:
- they're defined with
let
/let!
instead ofsubject
/subject!
- they do not set the anonymous
subject
or allow expectations to be called on it implicitly.
When to use them
It's completely legitimate to use let
to reduce duplication among examples. However, do so only when it doesn't sacrifice test clarity. The safest time to use let
is when the let
variable's purpose is completely clear from its name (so that the reader doesn't have to find the definition, which could be many lines away, to understand each example) and it is used in the same way in every example. If either of those things isn't true, consider defining the object in a plain old local variable or calling a factory method right in the example.
let!
is risky, because it's not lazy. If someone adds an example to the example group that contains the let!
, but the example doesn't need the let!
variable,
- that example will be hard to understand, because the reader will see the
let!
variable and wonder whether and how it affects the example - the example will be slower than it needs to be, because of the time taken to create the
let!
variablle
So use let!
, if at all, only in small, simple example groups where it's less likely that future example writers will fall into that trap.
The single-expectation-per-example fetish
There is a common overuse of subjects or let
variables that's worth discussing separately. Some people like to use them like this:
describe 'Calculator' do
describe '#calculate' do
subject { Calculator.calculate }
it { is_expected.to be >= 0 }
it { is_expected.to be <= 9 }
end
end
(This is a simple example of a method that returns a number for which we need two expectations, but this style can have many more examples/expectations if the method returns a more complicated value that needs many expectations and/or has many side effects that all need expectations.)
People do this because they've heard that one should have only one expectation per example (which is mixed up with the valid rule that one should only test one method call per example) or because they're in love with RSpec trickiness. Don't do it, whether with an anonymous or named subject or a let
variable! This style has several problems:
- The anonymous subject isn't the subject of the examples — the method is the subject. Writing the test this way screws up the language, making it harder to think about.
- As always with one-line examples, there isn't any room to explain the meaning of the expectations.
- The subject has to be constructed for each example, which is slow.
Instead, write a single example:
describe 'Calculator' do
describe '#calculate' do
it "returns a single-digit number" do
result = Calculator.calculate
expect(result).to be >= 0
expect(result).to be <= 9
end
end
end
When to use RSpec's let! instead of before?
No, it is not a best practice to always use let!
rather than a before
block. You're correct:
- If you want to run a block of code before every example, and you need that code to return a value, use
let!
. - If you want to run a block of code before every example, but you don't need to pass a value from that code directly to your example, use
before
. (Usinglet!
would mislead the reader into thinking that its value was being used.)
Note that both let!
and before
blocks should be used sparingly in any case. They create the risk of later test writers adding tests in the same block that don't need the result of the let!
or before
, making those tests harder to understand and slower.
RSpec: What is the difference between let and a before block?
People seem to have explained some of the basic ways in which they differ, but left out before(:all)
and don't explain exactly why they should be used.
It's my belief that instance variables have no place being used in the vast majority of specs, partly due to the reasons mentioned in this answer, so I won't mention them as an option here.
let blocks
Code within a let
block is only executed when referenced, lazy loading this means that ordering of these blocks is irrelevant. This gives you a large amount of power to cut down on repeated setup through your specs.
One (extremely small and contrived) example of this is:
let(:person) { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }
context 'with a moustache' do
let(:has_moustache) { true }
its(:awesome?) { should be_true }
end
context 'without a moustache' do
let(:has_moustache) { false }
its(:awesome?) { should be_false }
end
You can see that has_moustache
is defined differently in each case, but there's no need to repeat the subject
definition. Something important to note is that the last let
block defined in the current context will be used. This is good for setting a default to be used for the majority of specs, which can be overwritten if needed.
For instance, checking the return value of calculate_awesome
if passed a person
model with top_hat
set to true, but no moustache would be:
context 'without a moustache but with a top hat' do
let(:has_moustache) { false }
let(:person) { build(:person, top_hat: true) }
its(:awesome?) { should be_true }
end
Another thing to note about let blocks, they should not be used if you're searching for something which has been saved to the database (i.e. Library.find_awesome_people(search_criteria)
) as they will not be saved to the database unless they have already been referenced. let!
or before
blocks are what should be used here.
Also, never ever use before
to trigger execution of let
blocks, this is what let!
is made for!
let! blocks
let!
blocks are executed in the order they are defined (much like a before block). The one core difference to before blocks is that you get an explicit reference to this variable, rather than needing to fall back to instance variables.
As with let
blocks, if multiple let!
blocks are defined with the same name, the most recent is what will be used in execution. The core difference being that let!
blocks will be executed multiple times if used like this, whereas the let
block will only execute the last time.
before(:each) blocks
before(:each)
is the default before block, and can therefore be referenced as before {}
rather than specifying the full before(:each) {}
each time.
It's my personal preference to use before
blocks in a few core situations. I will use before blocks if:
- I'm using mocking, stubbing or doubles
- There is any reasonable sized setup (generally this is a sign your factory traits haven't been setup correctly)
- There's a number of variables which I don't need to reference directly, but are required for setup
- I'm writing functional controller tests in rails, and I want to execute a specific request to test (i.e.
before { get :index }
). Even though you could usesubject
for this in a lot of cases, it sometimes feels more explicit if you don't require a reference.
If you find yourself writing large before
blocks for your specs, check your factories and make sure you fully understand traits and their flexibility.
before(:all) blocks
These are only ever executed once, before the specs in the current context (and its children). These can be used to great advantage if written correctly, as there are certain situations this can cut down on execution and effort.
One example (which would hardly affect execution time at all) is mocking out an ENV variable for a test, which you should only ever need to do once.
Hope that helps :)
Rails - RSpec - Difference between let and let!
It's not invoked when defined, but rather before each example (and then it's memoized and not invoked again by the example). This way, count will have a value of 1.
Anyway, if you have another example, the before hook is invoked again - all of the following tests pass:
$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
How do RSpec's let and let! replace the original parameters?
The difference is that lets are lazily evaluated and then memoized for the rest of the spec.
So in the first example, first the before blocks run and set the values of @resource and @type, and then the spec runs.
In the second example, the spec runs and when it references 'resource' the let block runs and returns a value, then when 'type' is referenced is runs the let block for that. The let block for type itself references 'resource' so it gets the value for resource that was memoized from the first time resource was referenced.
For what its worth, I disagree that lets are 'better'. My team and I have found that all they do is make specs much harder to understand for very little benefit, and we have removed all use of them in all our projects.
In fact, I consider that most of 'better specs' is actually poor advice, so if you are struggling to understand why something is 'better', you are very much not alone :)
Why does this RSpec example require let! instead of let?
let
is lazy. If you don't reference it, it doesn't get evaluated and, in your case, side effects don't happen (side effect being the creation of database entry).
let!
, on the other hand, is always evaluated.
Rspec how to determine if a let block has been defined?
Assuming that you call let
before including the shared examples, this would work:
shared_examples 'a shared example' do
let(:some) { 'fallback value' } unless method_defined?(:some)
end
describe 'with let' do
let(:some) { 'explicit value' }
include_examples 'a shared example'
it { expect(some).to eq('explicit value') }
end
describe 'without let' do
include_examples 'a shared example'
it { expect(some).to eq('fallback value') }
end
method_defined?
checks if a method called some
has already been defined in the current context. If not, the method is defined to provide a default value.
Another (usually easier) approach is to always define a default value and to provide the explicit value after including the shared examples: (thus overwriting the default value)
shared_examples 'a shared example' do
let(:some) { 'default value' }
end
describe 'with let' do
include_examples 'a shared example' # <- order is
let(:some) { 'explicit value' } # important
it { expect(some).to eq('explicit value') }
end
describe 'without let' do
include_examples 'a shared example'
it { expect(some).to eq('default value') }
end
Rspec - Using variable defined in a let statement in another let statement
let(:name1) { "#{testcase['Car']}_#{Time.now.round}" }
let(:car1) { { name1: name1, car_model: testcase['Toyota'] } }
let(:car2) { { name2: name1 } }
So name1
is also now a lazy variable to initialized when is called, if not for car1, then for car2.
If the Time.now is a problem, you can leave the value of name1 as testcase['Car']
and then just interpolate the value of Time.now.
Related Topics
Can't Install Rmagick 2.13.1. Can't Find Magickwand.H.
How to Implement an Abstract Class in Ruby
An How to Support Tags in a Jekyll Blog
Submit Post Data from Controller to Another Website in Rails
Calling a Method from a String With the Method'S Name in Ruby
Methods in Ruby: Objects or Not
Best Explanation of Ruby Blocks
How to Break Outer Cycle in Ruby
Ruby'S Double Colon (::) Operator Usage Differences
Is There a Clean Way to Avoid Calling a Method on Nil in a Nested Params Hash
Understanding [Classone, Classtwo].Each(&:My_Method)
How to Break Out from a Ruby Block
In Rails, How to Render Json Using a View