Mohamad El-Husseini
2012-Mar-13 23:55 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
The following are what I believe two ways of doing the same thing. Only the first example fails, while the latter passes. I''m finding it hard to get to understand describe and context blocks, particularly with respect to scopes. I would appreciate any "for dummies" explanation, or a link to a blog post that can clear this up for me. I''m still new to Rails and Ruby. describe "send password reset" do let(:user) { FactoryGirl.create(:user) } # This fails context "generates a unique password_reset_token each time" do let(:user) { FactoryGirl.create(:user) } before do user.send_password_reset last_token = user.password_reset_token user.send_password_reset end its(:password_reset_token) { should_not == last_token } end # This passes it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not == last_token end end The first example fails with this: Failure/Error: its(:password_reset_token) { should_not == last_token } NameError: undefined local variable or method `last_token'' for #<RSpec::Core::ExampleGroup::Nested_6::Nested_7::Nested_1::Nested_1:0x0000000561e130> Earlier, it out puts this: should not When you call a matcher in an example without a String, like this: specify { object.should matcher } or this: it { should matcher } RSpec expects the matcher to have a #description method. You should either add a String to the example this matcher is being used in, or give it a description method. Then you won''t have to suffer this lengthy warning again. (FAILED - 1) Another thing I noticed is that I can not use capybara inside of describe blocks unless the calls are in a before block... but I don''t understand why. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120313/8c2f3cbe/attachment.html>
Mike Mazur
2012-Mar-14 00:24 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
Hi, On Wed, Mar 14, 2012 at 07:55, Mohamad El-Husseini <husseini.mel at gmail.com> wrote:> The following are what I believe two ways of doing the same thing. Only the > first example fails, while the latter passes.In your failing example: context "generates a unique password_reset_token each time" do let(:user) { FactoryGirl.create(:user) } before do user.send_password_reset last_token = user.password_reset_token user.send_password_reset end its(:password_reset_token) { should_not == last_token } end The `last_token` variable is in scope in the before block, but not in the its block. You can fix this by changing it to an instance variable: context "generates a unique password_reset_token each time" do let(:user) { FactoryGirl.create(:user) } before do user.send_password_reset @last_token = user.password_reset_token user.send_password_reset end its(:password_reset_token) { should_not == @last_token } end Another gotcha is: what is the its expression making assertions on? The its method requires a subject to be defined. RSpec defines an implicit subject based on the described class. I imagine at the top of your .spec file you have something like: describe User do # stuff end RSpec will generate a subject by calling `User.new`. `password_reset_token` is then called on this new user instance in the its block. You most certainly wanted to call `password_reset_token` on the user object defined by the `let` statement. So I think this should do the trick: context "generates a unique password_reset_token each time" do let(:user) { FactoryGirl.create(:user) } subject { user } before do user.send_password_reset @last_token = user.password_reset_token user.send_password_reset end its(:password_reset_token) { should_not == @last_token } end You can read more about RSpec''s subject here: https://www.relishapp.com/rspec/rspec-core/docs/subject Hope that helps, Mike
Mohamad El-Husseini
2012-Mar-15 12:46 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
Thanks, Mike. I appreciate the explanation. It''s tricky knowing what runs when, and what variable is in what scope. It seems like "code smell" to add an instance variable to the before block. I don''t understand what advantage one approach has over the other. What would you use, the first, that was broken, or the second? On Tuesday, March 13, 2012 9:24:03 PM UTC-3, Mike Mazur wrote:> > Hi, > > On Wed, Mar 14, 2012 at 07:55, Mohamad El-Husseini > <husseini.mel at gmail.com> wrote: > > The following are what I believe two ways of doing the same thing. Only > the > > first example fails, while the latter passes. > > In your failing example: > > context "generates a unique password_reset_token each time" do > let(:user) { FactoryGirl.create(:user) } > before do > user.send_password_reset > last_token = user.password_reset_token > user.send_password_reset > end > its(:password_reset_token) { should_not == last_token } > end > > The `last_token` variable is in scope in the before block, but not in > the its block. You can fix this by changing it to an instance > variable: > > context "generates a unique password_reset_token each time" do > let(:user) { FactoryGirl.create(:user) } > before do > user.send_password_reset > @last_token = user.password_reset_token > user.send_password_reset > end > its(:password_reset_token) { should_not == @last_token } > end > > Another gotcha is: what is the its expression making assertions on? > The its method requires a subject to be defined. > > RSpec defines an implicit subject based on the described class. I > imagine at the top of your .spec file you have something like: > > describe User do > # stuff > end > > RSpec will generate a subject by calling `User.new`. > `password_reset_token` is then called on this new user instance in the > its block. You most certainly wanted to call `password_reset_token` on > the user object defined by the `let` statement. > > So I think this should do the trick: > > context "generates a unique password_reset_token each time" do > let(:user) { FactoryGirl.create(:user) } > subject { user } > before do > user.send_password_reset > @last_token = user.password_reset_token > user.send_password_reset > end > its(:password_reset_token) { should_not == @last_token } > end > > You can read more about RSpec''s subject here: > > https://www.relishapp.com/rspec/rspec-core/docs/subject > > Hope that helps, > Mike > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users > >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20120315/79e4e690/attachment.html>
Michael Kintzer
2012-Mar-15 14:53 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
In your first example, last_token is declared in a before block, which means you need to mark it as an instance variable of the ExampleGroup ''@'' to reference it in the examples within the context. You don''t need to do that when you use let. Also you don''t need to declare the redundant let creating a user in that first context, since it was declared outside of the context already. HTH Michael On Mar 13, 2012, at 4:55 PM, Mohamad El-Husseini <husseini.mel at gmail.com> wrote:> The following are what I believe two ways of doing the same thing. Only the first example fails, while the latter passes. I''m finding it hard to get to understand describe and context blocks, particularly with respect to scopes. I would appreciate any "for dummies" explanation, or a link to a blog post that can clear this up for me. I''m still new to Rails and Ruby. > > describe "send password reset" do > let(:user) { FactoryGirl.create(:user) } > > # This fails > context "generates a unique password_reset_token each time" do > let(:user) { FactoryGirl.create(:user) } > before do > user.send_password_reset > last_token = user.password_reset_token > user.send_password_reset > end > its(:password_reset_token) { should_not == last_token } > end > > # This passes > it "generates a unique password_reset_token each time" do > user.send_password_reset > last_token = user.password_reset_token > user.send_password_reset > user.password_reset_token.should_not == last_token > end > end > > The first example fails with this: > > Failure/Error: its(:password_reset_token) { should_not == last_token } > NameError: > undefined local variable or method `last_token'' for #<RSpec::Core::ExampleGroup::Nested_6::Nested_7::Nested_1::Nested_1:0x0000000561e130> > > Earlier, it out puts this: > > should not When you call a matcher in an example without a String, like this: > > specify { object.should matcher } > > or this: > > it { should matcher } > > RSpec expects the matcher to have a #description method. You should either > add a String to the example this matcher is being used in, or give it a > description method. Then you won''t have to suffer this lengthy warning again. > (FAILED - 1) > > > Another thing I noticed is that I can not use capybara inside of describe blocks unless the calls are in a before block... but I don''t understand why. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Zach Dennis
2012-Mar-15 14:54 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
On Thu, Mar 15, 2012 at 8:46 AM, Mohamad El-Husseini <husseini.mel at gmail.com> wrote:> Thanks, Mike. I appreciate the explanation. It''s tricky knowing what runs > when, and what variable is in what scope. It seems like "code smell" to add > an instance variable to the before block. > > I don''t understand what advantage one approach has over the other. What > would you use, the first, that was broken, or the second?I am assuming you''re asking about what the advantage of using #subject vs. just #user is? If so, here are some advantages of using #subject IMO: RSpec will provide an implicit #subject already and in many cases you don''t need to construct a new one. This gets rid of an unnecessary line in those instances. Here''s an example from the docs: describe Array do it "should be empty when first created" do subject.should be_empty end end See https://www.relishapp.com/rspec/rspec-core/docs/subject/implicitly-defined-subject for more info. Rspec''s one liner syntax uses #subject, straight out of the docs is a great example: describe Array do describe "with 3 items" do subject { [1,2,3] } it { should_not be_empty } end end See https://www.relishapp.com/rspec/rspec-core/v/2-8/docs/subject/implicit-receiver for more information. Also see https://www.relishapp.com/rspec/rspec-core/v/2-8/docs/subject/attribute-of-subject Since #subject is an RSpec convention when writing shared example groups they are often set up to expect subject to be defined by the including example group. Here''s an example practically from the docs: shared_examples "a measurable object" do |value| it "should return #{value} from #length" do subject.send(:length).should ==value end end describe Array, "with 3 items" do subject { [1, 2, 3] } it_should_behave_like "a measurable object", 3 end See https://www.relishapp.com/rspec/rspec-core/v/2-8/docs/example-groups/shared-examples for more info. And since #subject is an RSpec convention, it always implies the "thing" under test. So, if you want to know what you''re testing you can either do a visual scan for a subject block or simply look at the class/module being described. Those are the reasons I find #subject to have advantage. By themselves, none of these are huge reasons, but combined, and over time, I have found relying on convention and getting a few extra niceties in my specs outweighs going against the convention and trying to reinvent conventions when I hit things like one-liners or shared examples/contexts. My 2 cents, Zach> > > On Tuesday, March 13, 2012 9:24:03 PM UTC-3, Mike Mazur wrote: >> >> Hi, >> >> On Wed, Mar 14, 2012 at 07:55, Mohamad El-Husseini >> <husseini.mel at gmail.com> wrote: >> > The following are what I believe two ways of doing the same thing. Only >> > the >> > first example fails, while the latter passes. >> >> In your failing example: >> >> ? ?context "generates a unique password_reset_token each time" do >> ? ? ? let(:user) { FactoryGirl.create(:user) } >> ? ? ? before do >> ? ? ? ? user.send_password_reset >> ? ? ? ? last_token = user.password_reset_token >> ? ? ? ? user.send_password_reset >> ? ? ? end >> ? ? ? its(:password_reset_token) { should_not == last_token } >> ? ? end >> >> The `last_token` variable is in scope in the before block, but not in >> the its block. You can fix this by changing it to an instance >> variable: >> >> ? ?context "generates a unique password_reset_token each time" do >> ? ? ? let(:user) { FactoryGirl.create(:user) } >> ? ? ? before do >> ? ? ? ? user.send_password_reset >> ? ? ? ? @last_token = user.password_reset_token >> ? ? ? ? user.send_password_reset >> ? ? ? end >> ? ? ? its(:password_reset_token) { should_not == @last_token } >> ? ? end >> >> Another gotcha is: what is the its expression making assertions on? >> The its method requires a subject to be defined. >> >> RSpec defines an implicit subject based on the described class. I >> imagine at the top of your .spec file you have something like: >> >> ? describe User do >> ? ? # stuff >> ? end >> >> RSpec will generate a subject by calling `User.new`. >> `password_reset_token` is then called on this new user instance in the >> its block. You most certainly wanted to call `password_reset_token` on >> the user object defined by the `let` statement. >> >> So I think this should do the trick: >> >> ? ?context "generates a unique password_reset_token each time" do >> ? ? ? let(:user) { FactoryGirl.create(:user) } >> ? ? ? subject { user } >> ? ? ? before do >> ? ? ? ? user.send_password_reset >> ? ? ? ? @last_token = user.password_reset_token >> ? ? ? ? user.send_password_reset >> ? ? ? end >> ? ? ? its(:password_reset_token) { should_not == @last_token } >> ? ? end >> >> You can read more about RSpec''s subject here: >> >> ? https://www.relishapp.com/rspec/rspec-core/docs/subject >> >> Hope that helps, >> Mike >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users-- -- @zachdennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Mike Mazur
2012-Mar-17 11:24 UTC
[rspec-users] Can some one please explain why one of those two examples fails?
Hi, On Thu, Mar 15, 2012 at 20:46, Mohamad El-Husseini <husseini.mel at gmail.com> wrote:> Thanks, Mike. I appreciate the explanation. It''s tricky knowing what runs > when, and what variable is in what scope. It seems like "code smell" to add > an instance variable to the before block. > > I don''t understand what advantage one approach has over the other. What > would you use, the first, that was broken, or the second?I would probably use `expect`[1]: describe "send password reset" do let(:user) { FactoryGirl.create(:user) } it "generates a unique password_reset_token" do expect { user.send_password_reset }.to change(user, :password_reset_token) end # Or if you want to make sure a token is generated twice it "generates a unique password_reset_token each time" do user.send_password_reset expect { user.send_password_reset }.to change(user, :password_reset_token) end # or perhaps it "generates a unique password_reset_token each time" do 2.times do expect { user.send_password_reset }.to change(user, :password_reset_token) end end end Mike [1]: https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/expect-change