Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enabling `let` to assign a formal parameter in case of block passed to RSpec shared example

Tags:

ruby

rspec

In RSpec, one of the techniques for providing a value to a shared example is to use let to define a variable used by that example, as described in this documentation.

Currently, the values passed into formal parameters take precedence over the any values established by let, as shown in the following example:

shared_examples_for "foo" do |x1|
  specify {puts [x1, x2, x3].inspect}
end

describe "" do
  let(:x1) {3}
  let(:x2) {3}
  let(:x3) {3}
  it_behaves_like "foo", 1 do
    let(:x1) {2}
    let(:x2) {2}
  end
end

# => [1, 2, 3]

My question is whether it would be possible/reasonable/desirable to change the RSpec semantics such that the variables set by let inside of the passed block would take precedence over the passed parameters and the above would output [2, 2, 3]. This would allow "simple" values to be passed in as parameters and more complicated values to be set via a block.

like image 978
Peter Alfvin Avatar asked Jan 03 '14 16:01

Peter Alfvin


1 Answers

use let to define a variable

let does not define variables; it defines a memoized helper method. The distinction is important, as ruby treats variables and methods differently. Specifically, local variables (e.g. the x1 block arg in your shared_examples_for block) always take precedence over methods of the same name, unless you use self.x1 to make it explicit that you are sending a message.

So, to answer your question: no, what you're asking is not possible. I don't think it's desirable, either; having local variables always "win" when there is a name collision is super important for being able to reason about your code. Consider the repurcssions if that was not the case. Let's say you started with this code:

# in superclass.rb
class Superclass
end

# in subclass.rb
class Subclass < Superclass
  def do_something(name)
    # do something with the `name` variable
  end
end

At some future point, Superclass is updated to have a name method:

class Superclass
  def name
    "superclass name"
  end
end

If ruby did not give precedence to local variables over methods, the Subclass#do_something method would get broken by an unrelated change to Superclass. The fact that local variables always take precedence means that you can more easily reason about what code does without worrying about changing in some distant code suddenly causing a particular identifier to be re-bound to a method rather than a local variable.

like image 80
Myron Marston Avatar answered Oct 13 '22 09:10

Myron Marston