visit
In RSpec, there are two different ways to write DRY tests, by using before or let. Their purpose is to create variables that are common across tests. In this post, we will explore differences between before and let and explain why let is preferred by the Ruby community.
$count = 0
describe "let" do
let(:count) { $count += 1 }
it "stores the value" do
expect(count).to eq(1)
expect(count).to eq(1)
end
it "is not cached across examples" do
expect(count).to eq(2)
end
end
Let should not be used for local variables, which have to be saved to the database, as they will not be saved to the database unless they have already been referenced. In this case, you should use let! or before blocks.
The the tests we are creating are done using .Also, never have a let block inside of a before the block, this is what let! is made for!
Unlike let, you can use let! to force the method's invocation before each example. It means that, even if you didn't invoke the helper method inside the example, it will be invoked before your example runs.
$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
expect(invocation_order).to eq([:let!, :example])
expect(count).to eq(1)
end
end
As with let blocks, if multiple let! blocks are defined with the same name, the most recent one will be executed. The core difference is that let! blocks will be executed multiple times if used like this, whereas the let block will only execute the last time.
class User
def tests
@tests ||= []
end
end
describe User do
before(:each) do
@user = User.new
end
describe "initialized in before(:each)" do
it "has 0 tests" do
@user.should have(0).tests
end
it "can accept new tests" do
@user.tests << Object.new
end
it "does not share state across examples" do
@user.should have(0).tests
end
end
end
In nearly every situation, it is better to use let over before blocks. Depending on your personal preference you could use before blocks when:
class User
def tests
@tests ||= []
end
end
describe User do
before(:all) do
@user = User.new
end
describe "initialized in before(:all)" do
it "has 0 tests" do
@user.should have(0).tests
end
it "can get accept new tests" do
@user.tests << Object.new
end
it "shares state across examples" do
@user.should have(1).tests
end
end
end
Using before(:all) in RSpec will cause you lots of trouble unless you know what you are doing! It runs outside of transactions, so the data created here will bleed into other specs.
Let blocks bring more to the table than before blocks. It all depends on what and how you need to make the work or consider using for creating data.
Besides being slower, one of the major problems with before blocks is that spelling errors can lead to bugs and false positives, allowing certain types of tests to pass when they shouldn't.before(:each) do
@user = User.find(username: "kolosek")
@user.logout
end
it "should log the user out" do
expect(@usr).to be_nil
end
Previously published at //kolosek.com/rspec-let-vs-before/