Rspec 1 - nested examples (in an each block) use the final block variable 4 times instead of each of

Please correct me if this is fixed in Rspec 2, but in Rspec 1 I have
hit upon the following problem (at least thrice, this time it costing
me several hours), code is the best example:

I have a practice examination system where each record is one of
several “subtests” (think tagging rather than subclassing) which is
part of a constant string array. I want to test each’s behaviour
indepently in my specs (since an earlier implementation used STI
rather than tagging, and I want to test each kind of test follows the
spec)

My specs use this approach in parts:

describe “#add_question_set_of_type” do
MyModule::SUBTESTS.each do |subtest|

describe %Q("#{subtest}") do
… specs using subtest

end
end

where SUBTESTS is a constant array of strings.

the problem is doing this breaks any blocks that look like

let(:subtest) {MyModule.subtest_to_sym(subtest)}

or

before(:each) do
@subtest = MyModule.subtest_to_sym(subtest)
end

Instead I have to avoid the @ or let examples, and use the more
explicit (verbose):

MyModule.subtest_to_sym(subtest)

using the let or before @ approaches both fail.

Using puts within the code (for debugging purposes) I found that the
specs were only being exposed to the final value of the array, while I
expected (and have seen, or at least assumed from past passes) that
all 4 (or so) of the strings were being used to create unique methods
on the example group (one per iteration).

Is this something that should be:

a. avoided, because it’s crazy, and written differently
b, documented
c. investigated further

I can provide a more complete example if helpful?

Thanks,
Nick

On Jul 28, 2010, at 5:42 PM, nruth wrote:

where SUBTESTS is a constant array of strings.

the problem is doing this breaks any blocks that look like

Where are these blocks, in the outer or inner example group? Please
provide a complete example, including everything I need to run and see
the output you’re seeing.

Thx

Hi David

I think this was a case of idiot-end-user (or developer) on my part,
sorry about that.

I’ve produced a simple example here (and figured out what I was doing
wrong).

I don’t think anything needs to change, though a wrapper function
(each + an inner describe) might help flag it as a possible pitfall.

Thanks,
Nick

On 30 Jul 2010, at 10:00 PM, nruth wrote:

how not to loop/nest rspec example groups (where you want to iterate diferent let/before variable values) · GitHub

I don’t think anything needs to change, though a wrapper function
(each + an inner describe) might help flag it as a possible pitfall.

Hi Nick

I think the “before + ivar” pattern (below) is on its way out. At
least, I consider it deprecated in my head:

describe “before” do
before(:each) { @foo = “bar” }
before(:each) { @foo = “baz” }

it "does this" do
  @foo.should eq "baz"
end

end

But the new syntax does indeed let you redefine let blocks.

describe “let” do
let(:foo) { “bar” }
let(:foo) { “baz” }

it "does this (currently)" do
  foo.should eq "baz"
end

end

Arguably, this is not the most desirable behaviour, as it leads to
silent/confusing failures, as you’ve seen. Maybe it would be better if
this raised an error? (Which is only an option with let, not with
ivars.)

Also, when combined with shared examples, this could conceivably be used
to violate the Liskov Substitution Principle (if the LSP applies to
specs). While Ruby as a language lets you redefine stuff at will, I’m
not sure that’s appropriate here. I can’t imagine wanting to redefine a
let, instead of factoring it out properly. Anyone got any thoughts on
that?

Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 31 Jul 2010, at 2:08 AM, nruth wrote:

If I want to set up a context to run some examples in (models created
with machinist, associations, etc) then the before block makes it
clear that that’s the state the examples are running against, and the
@vars give me a (quick and dirty?) hook to refer back to parts of that
setup (e.g. associated models) in the spec examples.

Of course, this can be done with let as well, but since they are lazy-
evaluated you can end up with your examples running in the wrong
context. For example: 501517’s gists · GitHub

Hi Nick

I’ve been bitten by that too :slight_smile: However, RSpec 2 comes to the rescue
with let!, which evaluates the block immediately, so you don’t have to
call the method to get it evaluated.

It’s interesting though, I’ll have to sit down some time and think
through what the semantics of before, let and let! mean to me…

it_should_behave_like “adding a question set”
end

etc…

perhaps not ideal if another test is added in later, and I wanted
coverage of that too for free, but it avoided the immediate problem &
is more clear what’s going on.

Hmm, without seeing the rest of your code it’s hard to say, but this
looks like a case of Control Couple. Is there a case statement inside?
If not is there any reason not to have methods
#add_abstract_reasoning_question_set,
#add_decision_analysis_question_set? If you’re not branching on the
parameter, how does the different behaviour come about?

I certainly prefer let and subject to @vars for shared examples -
again both work, but I find subject & lets provide a clear interface
for the shared example groups which I felt was lacking when passing
@variables around.

I agree. I’m going to see if I can tighten this up today with a DSL to
explicitly name the requirements of a shared example. (See the thread
“Evaluating shared example customisation block before shared block”.)

enforceable?

Re: LSP, food for thought, nothing to add at present unfortunately.
Not sure what angle you’re approaching shared examples from with that
though, do you mean redefining lets inside of the shared example
groups, or something else?

Redefining lets anywhere actually. The case for doing it within an
example group is probably stronger, as you wrote (or can manage) all the
examples yourself. I guess if RSpec raised an error when a shared
example accidentally redefines something from the host group, that would
be noise, and force you to change your own code unnecessarily.
Hopefully, if shared examples can say “I require these things to be set
up”, that issues won’t arise. We’ll see :slight_smile:

Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 31 Jul 2010, at 2:08 AM, nruth wrote:

Re: error / warning message, at the same scope (i.e. an accident, as
in the op) then yes that could be quite useful for spotting mistakes.
I’m not so sure about in different blocks though, it’s probably
intentional there (different context).

I forgot to add: David - think it’s worth one or other of us filing a
ticket for this? Is this check something that is likely to make it into
RSpec?


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

Hi Ash

I’ve found let and before useful in different situations. I like let
as a ‘new feature’ but am not sure it replaces, or is necessarily
superior to, @vars in all cases for specs.

If I want to set up a context to run some examples in (models created
with machinist, associations, etc) then the before block makes it
clear that that’s the state the examples are running against, and the
@vars give me a (quick and dirty?) hook to refer back to parts of that
setup (e.g. associated models) in the spec examples.

Of course, this can be done with let as well, but since they are lazy-
evaluated you can end up with your examples running in the wrong
context. For example: 501517’s gists · GitHub

So after falling in love with let I’ve found sometimes it isn’t worth
the overhead & it’s just quicker and tidyer to use before/@ ? I wonder
whether in the end the only difference is the syntax highlighting,
couple of extra lines of code or @ character, and perhaps the time /
thought process in getting there.

back to op briefly, I ended up with this approach for the specs
instead:

describe “#add_question_set_of_type(‘Abstract Reasoning’)” do
let(:subtest) {‘Abstract Reasoning’}
it_should_behave_like “adding a question set”
end

describe “#add_question_set_of_type(‘Decision Analysis’)” do
let(:subtest) {‘Decision Analysis’}
it_should_behave_like “adding a question set”
end

etc…

perhaps not ideal if another test is added in later, and I wanted
coverage of that too for free, but it avoided the immediate problem &
is more clear what’s going on.

I certainly prefer let and subject to @vars for shared examples -
again both work, but I find subject & lets provide a clear interface
for the shared example groups which I felt was lacking when passing
@variables around.

Re: error / warning message, at the same scope (i.e. an accident, as
in the op) then yes that could be quite useful for spotting mistakes.
I’m not so sure about in different blocks though, it’s probably
intentional there (different context).

Re: redefining vs refactoring - I like what you’re saying (reminds me
of dry vs non-dry specs, and I find myself refactoring a lot of my old
spaghetti specs lately), but wonder if there are (edge) cases where
it’s useful or necessary. Maybe there’s some discussion dating back to
when the feature was added. It may be stylistic though, rather than
enforceable?

Re: LSP, food for thought, nothing to add at present unfortunately.
Not sure what angle you’re approaching shared examples from with that
though, do you mean redefining lets inside of the shared example
groups, or something else?

Cheers,
Nick

On Jul 30, 11:13 pm, Ashley M. [email protected]