Rspec let scoping
I found what works for me:
describe Connection do
it_behaves_like "any connection", new.connection
# new.connection: because we're in the class context
# and let creates method in the instance context,
# instantiate a instance of whatever we're in
end
Rspec let scoping with shared examples
Here in your example method re-defining has been done. When include_context 'a'
has been executed then it defines let_example
as a method which prints 'let_example a'
. But when you include_context 'b'
has been executed then redefines the method let_example
which would effectively prints let_example 'b'
.
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
RSpec: Accessing a `let` definition from within a block in an example
Use instance_exec
instead – it allows you to pass arguments into the block's scope:
my_object.instance_exec(my_var) { |v| @my_inst_var = v }
Alternatively, you could set the instance variable via instance_variable_set
:
my_object.instance_variable_set(:@my_inst_var, my_var)
Although both of the above work, altering the object's state that way can lead to brittle tests. You should consider changing the object so it becomes easier to test. (add a setter or pass the value upon initialization)
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
Rails Generate Has_Many Association
Ruby: Select a Hash from Inside an Array
Convert Datetime String to Utc in Rails
Creating Categories on Jekyll Driven Site
Get Substring After the First = Symbol in Ruby
Sort an Array of Strings by Their Integer Values
Use Multiple Versions of Rubygems with Rvm
Rails: Serializing Objects in a Database
Instance and Class Variables in Rails Controller
How to Load a Spec_Helper.Rb Automatically in Rspec 2
Disable Rvm or Use Ruby Which Was Installed Without Rvm
How Are Symbols Used to Identify Arguments in Ruby Methods
How to Remove/Disable Sign Up from Devise