Peter Alfvin
Peter Alfvin

Reputation: 29389

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

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.

Upvotes: 5

Views: 5040

Answers (2)

Myron Marston
Myron Marston

Reputation: 21800

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.

Upvotes: 5

mraaroncruz
mraaroncruz

Reputation: 3809

I would use contexts if I just wanted to change the context a bit with new lets.

You can use context blocks inside or outside describe blocks. The are essentially the same but create a way for you to semantically organize your specs a differently (IMO better)

describe "adding a credit card" do
  let(:card_number) { "123456789012" }

  it "accepts cards" do
    pending
    # do something with card_number
  end

  context "failure" do
    let(:card_number) { "BLUEBEARD666" }

    it "doesn't handle weird formats" do
      # oh yeah!
    end
  end
end

In this case you have your embedded lets but it makes more sense semantically.

I hope this doesn't miss the point entirely :P

Upvotes: 0

Related Questions