In Rspec, Using Let Variable Inside Before :All Block

In RSpec, using let variable inside before :all block

I had the same problem, I have solved it by declaring all my variables as attributes inside the before block:


describe 'index'

before(:all) do
@company = FactoryGirl.create(:company)
@user = FactoryGirl.create(:user, company: @company)

sign_in @user
visit products_path
end
...
end

Now you can use @user and @company inside your tests, and you shouldn't have any warnings.

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.

Rspec let variable in before block not changing between examples

That happens because internally, let uses define_method as seen in the source for let. You can create a quick example

class A
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
end

a = A.new
a.create_method(:foo) { puts "bar" }
a.create_method(:foo) { puts "baz" }
a.foo

and run that and you would see that define_method overrides the previous method with the new one. So in your example, you are creating a method and then overriding it's definition twice before you ever get a chance to call it.

You are wanting to run each error_message in it's own context like this:

def method_that_uses(e)
puts "running with: #{e}"
end

context 'my test context' do
before do
method_that_uses(error_message)
end

context 'error' do
let(:error_message) { 'error' }

it { puts "run one" }
end

context 'second error' do
let(:error_message) { 'different error' }

it { puts "run two" }
end

context' third error' do
let(:error_message) { 'third error' }

it { puts "run three" }
end
end

which, when run, outputs

running with: error
run one
.running with: different error
run two
.running with: third error
run three
.

This works because describe and context blocks create a new ExampleGroup (source) and ExampleGroup states

Example group bodies (e.g. describe or context blocks) are evaluated in the context of a new subclass of ExampleGroup.

So, let(:error_message) is now defining those methods on different subclasses.

Make rspec more DRY by accessing let variable in describe block

I assume response block works in the same way as context

post "Creates object", controller_action: :create do
let(:createBody) do
{
name: "yes",
type: "my_type",
amount_cents: 200,
parameters: {
day: "today"
},
user_group_ids: [],
user_ids: user_ids # now we can add a let(:user_ids) to each context
}
end

response 201, "object created" do
let(:user_ids) { [] } # empty since we're not using it here

...

run_test! do |res|
# this block works fine and can access createBody
body = JSON.parse(res.body)
expect(body["name"]).to eq(createBody[:name])
expect(body["amount_cents"]).to eq(createBody[:amount_cents])
expect(body["type"]).to eq(createBody[:type])
end
end

# with users
response 201, "autocharge rule created" do
let(:user_ids) { [1, 2, 3] } # add required ids

...

run_test! do |res|
# this block works fine and can access createBody
body = JSON.parse(res.body)
expect(body["user_ids"]).to match_array(createBody[:user_ids]) # we can match by user_ids now
end
end
...
end

rspec - what is the syntax of before :each and before :all vs let! and let

The 'classic' way of using before(:each) and before(:all) is

describe "tests" do
before :each do
@var1='a'
@var2='b'
end
end

You then refer to the instance variables by preceeding them with an @ sign inside the tests, i.e.

describe "tests" do
before :each do
@var1='a'
@var2='b'
end
it "does stuff" do
a=some_class.some_method(@var1)
expect(a).to eq @var2
end
end

If you want a variable to have the same value in each test you can use before :all.

If you want a variable to have the same value generated for each test use before :each.

Generally use before :each as improper use of before :all can too easily lead to intermittent test failures.

Think of before :all as defining constants.

The let/let! format looks like this:

describe "tests" do
let(:var1) {'a'}
let!(:var2) {srand(25)}
it "does stuff" do
a=some_class.some_method(var1)
expect(a).to eq var2
end
end

Notice several things:

  1. var1 and var2 now look like local variables, i.e. var1 instead of @var1 when used in the test and setup. In actual fact they are test class methods. The important thing to note for usage is that you do NOT use the @ sign when subsequently using them unlike the before :each and before :all where you did.
  2. In the first case of the let's example, let is used with var1. This allow the memoization across examples to work. In the second case let! is used with var2. This is needed because you want the variable (method) to be called/set explicitly before each use. srand() is a great example of when you would need this as it seeds the random number generator but only for the next use of rand() so if using it in multiple test examples resetting it is needed each time.

Updating/Reassigning values field in :let Variable in Rspec

What you want to do is use a let variable inside the let variable:

describe "#create" do
subject { post :create, params: animal_payload }

let(:animal_payload) {
{
"data": {
......
"animal_type": {
"data": {
"id": 1,
"type": animal_type,
}
},
}
}
}

context 'when sea animal' do
let(:animal_type) { 'sea' }

it "should create a sea animal" do
....
end
end

context 'when land animal' do
let(:animal_type) { 'land' }

it "should create a land animal" do
....
end
end
end

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. Since let creates a method, you'll get a NameError 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 by let, 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 my it block nice and short.

A related link can be found here: http://www.betterspecs.org/#let



Related Topics



Leave a reply



Submit