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
orcontext
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:
var1
andvar2
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
andbefore :all
where you did.- In the first case of the
let
's example,let
is used withvar1
. This allow the memoization across examples to work. In the second caselet!
is used withvar2
. 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 ofrand()
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. 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
Related Topics
Overriding Model in Gem, Adding Callback and Methods
Native Threads in Ruby 1.9.1, Whats in It for Me
How to Ssh Interactive Session
Rails 4 Error with Every Command "'Load': No Implicit Conversion of Nil into String" (MAC Os X 10.9)
Ruby: Automatically Set Instance Variable as Method Argument
What's the Nature of "Property" in a Ruby Class
How to Compare Private Attributes in Ruby
Bundler Could Not Find Compatible Versions for Gem "Bundler": in Gemfile:
Simple Remote Odbc Database Connection with Ruby
How to Install "Readline" for Rails Console
Rails Routing - Custom Routes for Resources
Rails 5 - Using Polymorphic Associations - Rendering the Views
What's the Difference Between the Ruby Irb Prompt Modes
Selenium Webdriver Getting a Cookie Value
Ruby Equivalent for Python's For/Else
What Are Fast Xml Parsers for Ruby
How to Specify a Regex Character Range That Will Work in European Languages Other Than English