Forum: RSpec let vs ivar w/ before block

Posted by Patrick Collins (patrick99e99)
on 2011-09-30 21:56
(Received via mailing list)
Am I correct in understanding that the point of using let is that it can
minimize database calls due to memoization?

describe "#something" do

  let(:something) = Something.groovy_scope

  it "does x" do
    something.first.x.should have_coolness
  end

  it "does y" do
   something.first.y.should_not have_coolness
  end

end

vs.

describe "#something" do

  before :each do
    @something = Something.groovy_scope
  end

  it "does x" do
   @something.first.x.should_not have_coolness
  end

  it "does y" do
   @something.first.y.should_not have_coolness
  end

end

...

Is that the proper usage?

Patrick J. Collins
http://collinatorstudios.com
Posted by Patrick Collins (patrick99e99)
on 2011-09-30 21:56
(Received via mailing list)
> Am I correct in understanding that the point of using let is that it can
> minimize database calls due to memoization?
>
> describe "#something" do
>
>   let(:something) = Something.groovy_scope

and obviously I meant let(:something) { ... }, not =

(oops)...

Patrick J. Collins
http://collinatorstudios.com
Posted by David Chelimsky (Guest)
on 2011-09-30 22:16
(Received via mailing list)
On Sep 30, 2011, at 2:48 PM, Patrick J. Collins wrote:

>
>  before :each do
>
> end
>
> ...
>
> Is that the proper usage?

Proper usage, sure, but the memoization is only within each example - 
not across examples. That way you can do this:

let(:thing) { Thing.new }

it "does something" do
  thing.blah
  thing.whatever
  thing.yet_again
end

In that case each reference to thing returns the same object.

Make sense?
Posted by Patrick Collins (patrick99e99)
on 2011-09-30 22:47
(Received via mailing list)
> In that case each reference to thing returns the same object.
>
> Make sense?

Hmm.. now I am confused...

What is the difference between:

describe "Foo" do

  let(:foo) { Foo.new }

  it "is tubular" do
    foo.bar
    foo.baz
  end

  it "is gnarly" do
    foo.gnarl
    foo.wurd_up
  end

end

vs.

describe "Foo" do

  before :each do
    @foo = Foo.new
  end

  it "seems just as tubular as the foo w/ let" do
    @foo.bar
    @foo.baz
  end

  it "seems just as gnarly as the foo w/ let" do
    foo.gnarl
    foo.wurd_up
  end

end

I am not seeing any difference...?


Patrick J. Collins
http://collinatorstudios.com
Posted by David Chelimsky (Guest)
on 2011-09-30 23:23
(Received via mailing list)
On Sep 30, 2011, at 3:31 PM, Patrick J. Collins wrote:

>> In that case each reference to thing returns the same object.
>
> end
>    @foo.bar
> I am not seeing any difference...?
There is not, really, other than how the declaration of foo is expressed 
and referenced. This evolved out of a common pattern in TDD:

1:

describe "something" do
  it "does something" do
    thing = Thing.new
    thing.do_something.should have_some_outcome
  end
end

2:

describe "something" do
  it "does something" do
    thing = Thing.new
    thing.do_something.should have_some_outcome
  end

  it "does something else" do
    thing = Thing.new
    thing.do_something_else.should have_some_other_outcome
  end
end

Now there is duplication so we can refactor out the declaration of 
thing. It takes less work and is less error prone to change it to a let 
declaration than to change the references to thing to an instance 
variable declared in a before hook.

Cheers,
David
Posted by Srushti Ambekallu (Guest)
on 2011-09-30 23:55
(Received via mailing list)
On 30/09/11 3:58 PM, David Chelimsky wrote:
>>> end
>>   let(:foo) { Foo.new }
>>
>>   it "seems just as tubular as the foo w/ let" do
>>
> end
>      thing = Thing.new
> rspec-users mailing list
> rspec-users@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>
One thing I noticed, and something that always catches me out is the
fact that the 'let'ted object doesn't get instantiated until it gets
referenced. Therefore,

describe "all" do
     let(:foo) { Foo.create! }

     it "returns the created object" do
         Foo.all.should include(foo)
     end
end

... fails, since at the time of calling :all, the 'foo' object hasn't
been referred yet, and hence the block hasn't executed. "Foo.all" in the
case above returns an empty array, which wouldn't have been the case
with an instance object created in "before(:each)".

Srushti
http://c42.in
Posted by David Chelimsky (Guest)
on 2011-10-01 00:07
(Received via mailing list)
On Sep 30, 2011, at 4:32 PM, Srushti Ambekallu wrote:

>>>>  thing.yet_again
>>>
>>>  end
>>>
>>> end
>>   end
>>   it "does something else" do
>> _______________________________________________
>        Foo.all.should include(foo)
>    end
> end
>
> ... fails, since at the time of calling :all, the 'foo' object hasn't been 
referred yet, and hence the block hasn't executed. "Foo.all" in the case above 
returns an empty array, which wouldn't have been the case with an instance object 
created in "before(:each)".
>
> Srushti
> http://c42.in

That's one of the benefits of let :) You can use it to declare objects 
but they only get instantiated when you reference them.

See 
https://www.relishapp.com/rspec/rspec-core/docs/he...
Posted by Patrick Collins (patrick99e99)
on 2011-10-01 01:29
(Received via mailing list)
> Proper usage, sure, but the memoization is only within each example - not
> across examples. That way you can do this:

So regarding objects persisting over multiple examples--  I was told 
repeatedly
by experienced RSpec peeps to not use before(:all)...

But in a case like:

before(:each) do
  @user = create_user
  create_user_item(:user => @user)
end

it "has an item" do
  @user.user_item.should_not be_nil
end

it "rocks the house" do
  @user.user_item.should respond_to(:rocks_the_house)
end

..etc...

It seems like this is an instance where before :all, really would shine 
because
it would not require records to be repeatedly created...

Do you guys feel like before(:all) is just bad because of the 
possibility of a
method call in one example changing the state and therefore breaking 
future
examples and not having it be clear as to why.. ?



Patrick J. Collins
http://collinatorstudios.com
Posted by Srushti Ambekallu (Guest)
on 2011-10-01 02:01
(Received via mailing list)
On 30/09/11 6:24 PM, Patrick J. Collins wrote:
> end
>
> http://collinatorstudios.com
> _______________________________________________
> rspec-users mailing list
> rspec-users@rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>
I don't know if object creation (including the corresponding database
calls) are really expensive enough that I would worry about optimising
that (unless you're getting it anyway, without losing anything in the
process, like with 'let'). Occasionally, for some reason I'll end up
using a 'before(:all)' for one isolated scenario, but inevitably a few
months down the line, I'll end up wasting a couple of hours on a weird
failing test, until I look closely enough to notice the ":all".

So, unless I'm doing something that's prohibitively expensive (I haven't
found anything that would qualify in any of my projects) I'm not likely
to create anything in a 'before(:all)'.

Srushti
http://c42.in
Posted by Ash Moran (Guest)
on 2011-10-01 13:30
(Received via mailing list)
On 1 Oct 2011, at 00:36, Srushti Ambekallu wrote:

> On 30/09/11 6:24 PM, Patrick J. Collins wrote:
>> So regarding objects persisting over multiple examples--  I was told repeatedly
>> by experienced RSpec peeps to not use before(:all)
>
> I don't know if object creation (including the corresponding database calls) are 
really expensive enough that I would worry about optimising that (unless you're 
getting it anyway, without losing anything in the process, like with 'let'). 
Occasionally, for some reason I'll end up using a 'before(:all)' for one isolated 
scenario, but inevitably a few months down the line, I'll end up wasting a couple 
of hours on a weird failing test, until I look closely enough to notice the 
":all".
>
> So, unless I'm doing something that's prohibitively expensive (I haven't found 
anything that would qualify in any of my projects) I'm not likely to create 
anything in a 'before(:all)'.

I'll second that. It's better to feel the pain of the multiple database 
hits and refactor it out in the code (can you separate more of the logic 
and persistence?), than try to optimise the test run by introducing 
shared state. In the long run, the former will give you many faster test 
runs through better design, where as the latter will give you a few 
faster tests now at the risk of - as Srushti points out - stepping on a 
landmine later.

HTH

Ash

--
http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashmoran
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.