If you invoke any let
-defined method from a before(:all)
block, all let
values will be shared across all examples. A sample. This case is not specified one way or the other in the documentation, but the observed behavior is a bit surprising. I think a less surprising behavior would be for the let
s to be memoized separately for before(:all)
.
This behavior occurs in all RSpec 2.x versions (I tested all the way back to 2.0.1 and up to 2.8.0.rc1), but I can find no mention of it in the mailing list or via google, so I guess that it doesn't come up too often.
Agreed the current behavior is confusing, and that referencing one let
-defined method should not impact the others. There are two ways to go with this: memoize let
s in before(:all)
separately, as you suggest, or decouple them entirely. i.e. let
always memoizes values per example regardless of whether they are referenced in before(:all)
. Personally, the latter feels cleaner and less confusing to me, both as a user and a maintainer of the underlying code. WDTY?
I agree, David. If people want a helper method that memoizes a value in before(:all)
they can always do:
def my_helper
@my_helper ||= calculate_my_helper
end
before(:all) do
my_helper # force it to initialize
end
let
always memoizes values per example regardless of whether they are referenced inbefore(:all)
.
This would be fine by me and is the effect I was hoping to get out of my suggestion. It's probably my lack of familiarity with rspec's internals, but I'm not clear how this solution would be different from separately memoizing let
s in before(:all)
. Would it be that the let
s that were referenced from before(:all)
would be shared with the first example run? Or would let
s called from before(:all)
not be memoized? Or would it be that let
s wouldn't be callable from before(:all)
at all?
I can live with any of these cases, but it does seem possible that if let
s are shared between before(:all)
and the first example, you might break symmetry with after(:all)
(since presumably it would have access to the let
s as memoized in the last example).
I agree with @dchelimsky
@rsutphin - fantastic bug report (the gist), btw
I just ran into this, took me ages to see what was going on and I didn't realise I'd copied a before(:all)
from another file. There are two parts of this:
First, the invocation of one let
(here, the one used in the before(:all)
) interfering with other let
s is a bug IMO. Whatever else happens, one let
statement should not change the behaviour of others.
Second is the issue of how to handle let
values used in before(:all)
. I'm not keen on them being implicitly memoised separately, as then you can't see the scope of a let
without seeing where it is used (exactly the opposite of what the word "let" suggests to me, at least). One possible alternative would be to have an alternative let_for_all
method or similar that memoised across all examples and leave let
memoised per-example. Absent an explicit let_for_all
, I'm not sure what a let
in a before(:all)
even means. For example, how do you interpret this?
describe User do
let(:user) { User.new }
before(:all) do
setup(user.id)
end
it "does something" do
user.id.should do_something
end
it "does something else" do
user.id.should do_something_else
end
end
Implicitly memoising user
across examples is confusing, but if that's not done then the two examples above implicitly become order-dependent. Using the let
in before(:all)
there looks like a mistake. I've not dug into the code to see how feasible it would be, but raising an error might be the safest option. (It'd at least prevent anyone else having to track down this ticket!)
Not sure about the internals as I just started looking at the rspec-core code. Is this still an open issue that needs to be fixed by ensuring that let always memoizes values per example?
Personally, I think we should get rid of before(:all)
. It provides much more confusion and complexity than value.
I've used before(:all) before to perform expensive setup with integration tests (in the case I'm thinking of it was testing stuff acting on a dynamically defined db schema, which only needed to be done before all the tests as it was performed by independent code) and whilst this was only to support legacy code that I didn't write.... it did help massively...
I doubt I'm alone in that, so while from a personal 'design' perspective I'd like to see it go I wouldn't remove it unless it's a significant benefit in terms of maintenance overhead for rspec?
@JonRowe Only "shared instance variables" will go away. Right now, RSpec stores the ivars set in before(:all)
and makes them available in examples and after(:all)
's. The functionality of "execute this block before all of these examples" will stick around in the form of before(:group)
.
@justinko That seems fair, prevent shared state across all examples whilst still allowing a one-time setup action for a particular group of examples.
@JonRowe Exactly.
Has there been any work on before(:group) ?
@justinko how is before(:all) making instance variables available?
Has there been any work on before(:group) ?
Not yet, no.
Oh yeah just tripped over this one that took me a good few hours to diagnose, still trying to figure out a workaround for it.
Since this is still an open issue one year later, maybe it should be considered an exceptional case referencing a let
-defined value in a before :all
block?
This came up again in #802 and I've got a handle for how we're going to solve it, so I'm going to close this in favor of the discussion there since that contains the details of the approach I'm taking.
Most helpful comment
I've used before(:all) before to perform expensive setup with integration tests (in the case I'm thinking of it was testing stuff acting on a dynamically defined db schema, which only needed to be done before all the tests as it was performed by independent code) and whilst this was only to support legacy code that I didn't write.... it did help massively...
I doubt I'm alone in that, so while from a personal 'design' perspective I'd like to see it go I wouldn't remove it unless it's a significant benefit in terms of maintenance overhead for rspec?