Here is the code in question: describe UsersController do render_views … … describe "POST to ''create'' action" do describe "failure" do before(:each) do @attr = { :name => '''', :email => '''', :password => '''', :password_confirmation => '''' } end it "should not create a user" do lambda { post :create, :user => @attr }.should_not change(User, :count) end it "should have the right title" do post :create, :user => @attr response.should have_selector(:title, :content => ''Sign up'') end Comparing the last two tests, it looks to me like: lambda { post :create, :user => @attr }.should_not change(User, :count) should be equivalent to: post :create, :user => @attr response.should_not change(User, :count) But when I make that change, the test fails with this rror: Failures: 1) UsersController Post create failure should not create a user Failure/Error: response.should_not change(User, :count) NoMethodError: undefined method `call'' for #<ActionController::TestResponse:0x00000100f3d3a0> # ./spec/controllers/users_controller_spec.rb:62:in `block (4 levels) in <top (required)>'' -- Posted via http://www.ruby-forum.com/. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
John Feminella
2011-Jul-25 12:51 UTC
Re: What does using a lambda in rspec tests accomplish?
First, the test fails because it expects a Proc -- it''s trying to invoke #call. So you can''t do what you''re proposing without writing a new definition for how #change works. As a deeper answer, your "shouldn''t these be equivalent?" statement is not accurate. RSpec, like any piece of Ruby code, needs to know when it should start paying attention for the change(). In other words, it needs a "before" to start with and an "after" to compare to. With a lambda, this is accomplished by measuring the thing you want to check before the lambda is run, and then again after it''s run. If the difference matches what you expected, the test passes. If it doesn''t it fails. Your example is asking RSpec to do this: before.val = 5 before.val = 6 before.should change(:val).from(5).to(6) How can it know that `before.val` used to be 5? Answer: it can''t. By the time you reach the third line, the old value of `before.val` is gone forever. ~ jf -- John Feminella Principal Consultant, BitsBuilder LI: http://www.linkedin.com/in/johnxf SO: http://stackoverflow.com/users/75170/ On Mon, Jul 25, 2011 at 07:12, 7stud -- <lists-fsXkhYbjdPsEEoCn2XhGlw@public.gmane.org> wrote:> Here is the code in question: > > > describe UsersController do > render_views > … > … > > describe "POST to ''create'' action" do > describe "failure" do > before(:each) do > @attr = { :name => '''', :email => '''', :password => '''', > :password_confirmation => '''' } > end > > it "should not create a user" do > lambda { > post :create, :user => @attr > }.should_not change(User, :count) > end > > it "should have the right title" do > post :create, :user => @attr > response.should have_selector(:title, :content => ''Sign up'') > end > > > > > Comparing the last two tests, it looks to me like: > > > > lambda { > post :create, :user => @attr > }.should_not change(User, :count) > > should be equivalent to: > > post :create, :user => @attr > response.should_not change(User, :count) > > > But when I make that change, the test fails with this rror: > > > > Failures: > > 1) UsersController Post create failure should not create a user > Failure/Error: response.should_not change(User, :count) > NoMethodError: > undefined method `call'' for > #<ActionController::TestResponse:0x00000100f3d3a0> > # ./spec/controllers/users_controller_spec.rb:62:in `block (4 > levels) in <top (required)>'' > > -- > Posted via http://www.ruby-forum.com/. > > -- > You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. > To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org > To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. > For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en. > >-- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
John Feminella wrote in post #1012869:> In other words, it > needs a "before" to start with and an "after" to compare to. With a > lambda, this is accomplished by measuring the thing you want to check > before the lambda is run, and then again after it''s run. >Okay, I sort of sussed that out myself. But the question remains, how can should_not() expect a lambda in one case, and then successfully operate on a String in another case, like here: @user.encrypted_password.should_not be_blank Does the definition of should_not check the type of the receiver before executing? I can''t find any docs for the definition of should_not(). -- Posted via http://www.ruby-forum.com/. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
David Chelimsky
2011-Jul-25 23:18 UTC
Re: What does using a lambda in rspec tests accomplish?
On Jul 25, 6:40 pm, 7stud -- <li...-fsXkhYbjdPsEEoCn2XhGlw@public.gmane.org> wrote:> John Feminella wrote in post #1012869: > > > In other words, it > > needs a "before" to start with and an "after" to compare to. With a > > lambda, this is accomplished by measuring the thing you want to check > > before the lambda is run, and then again after it''s run. > > Okay, I sort of sussed that out myself. But the question remains, how > can should_not() expect a lambda in one case, and then successfully > operate on a String in another case, like here: > > @user.encrypted_password.should_not be_blank > > Does the definition of should_not check the type of the receiver before > executing? I can''t find any docs for the definition of should_not().should and should_not are both added to every object, and pass self (the receiver) to the matches?() method on the matcher passed to should[_not]. e.g. obj.should matcher # => matcher.matches?(obj) It is the matcher that cares what the object is, not the should[_not] method. And the matchers do not, generally, do type checking on the object - they just assume that you''re doing the right thing, and let Ruby handle errors if you do the wrong thing (e.g. undefined method ''call''). HTH, David -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
David Chelimsky wrote in post #1013001:> On Jul 25, 6:40pm, 7stud -- <li...-fsXkhYbjdPsEEoCn2XhGlw@public.gmane.org> wrote: >> >> @user.encrypted_password.should_not be_blank >> >> Does the definition of should_not check the type of the receiver before >> executing? I can''t find any docs for the definition of should_not(). > > should and should_not are both added to every object, and pass self > (the receiver) to the matches?() method on the matcher passed to > should[_not]. e.g. > > obj.should matcher # => matcher.matches?(obj) > > It is the matcher that cares what the object is, not the should[_not] > method. >Thanks for that. I was able to find some more details based on your post. The lambda construct in rspec can be simplified like this: a_function.should_not( change(User, :count ) The change() method simply returns a Change ''matcher'' object: def change(a_class, message) #(User, :count) Change.new(a_class, message) #(User, :count) end where the Change class is defined something like this: class Change def initialize(a_class, message) #(User, :count) @receiver = a_class @msg = message end def matches?(callable) #(a_function) before = @receiver.send(msg) #same as calling User.count() callable.call #execute a_function() after = @receiver.send(msg) #same as calling User.count() before == after end ... ... end should_not() is defined something like this: def should_not(obj) result = obj.matches?(self) ... ... end For references, see: 1) http://rspec.rubyforge.org/rspec/1.1.9/classes/Spec/Expectations.html ===Spec::Expectations adds two methods to Object: should(matcher=nil) should_not(matcher=nil) Both methods take an optional Expression Matcher (See Spec::Matchers). When should receives an Expression Matcher, it calls matches?(self)**. If it returns true, the spec passes and execution continues. If it returns false, then the spec fails with the message returned by matcher.failure_message. Similarly, when should_not receives a matcher, it calls matches?(self). If it returns false, the spec passes and execution continues. If it returns true, then the spec fails with the message returned by matcher.negative_failure_message. RSpec ships with a standard set of useful matchers, and writing your own matchers is quite simple. See Spec::Matchers for details. ==== **That can be made more clear: When should() receives an Expression Matcher, it calls expr_matcher.matches?(self): def should(expr_matcher) result = expr_matcher.matches?(self) … … end self is the object calling the should() method. If matches?() returns true, the spec passes and execution continues. If it returns false, then the spec fails with the message returned by expr_matcher.failure_message. 2) http://www.robertsosinski.com/2008/12/10/up-and-running-with-custom-rspec-matchers/ However, that tutorial contains this passage: ==3. Within the heart of our matcher is the matches? method. This method takes one parameter, the actual value of the object that the should or should_not method is passed to within your RSpec test. == That sentence is faulty ruby speak, and therefore doesn''t make any sense. It should say, ==3. … … This method takes one parameter, the actual value of the object that the should or should_not *message* is passed to within your RSpec test. == But in ruby, that simply means one thing: ==This method takes one parameter: the receiver of the should() or should_not method call() (where the receiver is the ''thing'' that is calling the method). == -- Posted via http://www.ruby-forum.com/. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Similarly, calling should_not on a string works like this: string.should_not(be_blank) def be_blank Blank.new end class Blank def matches?(string) @actual = string string.blank? end ... ... end And as David Chelimsky pointed out it is change() and be_blank() that care about what type of the object calling should and should_not--the object is either the correct type, or an error occurs. -- Posted via http://www.ruby-forum.com/. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.
Whoops. This: ==where the Change class is defined something like this: class Change def initialize(a_class, message) #(User, :count) @receiver = a_class @msg = message end def matches?(callable) #(a_function) before = @receiver.send(msg) #same as calling User.count() callable.call #execute a_function() after = @receiver.send(msg) #same as calling User.count() before == after end ... ... end == should look more like this: where the Change class is defined something like this: class Change def initialize(a_class, message) #(User, :count) @receiver = a_class @msg = message end def matches?(callable) #(a_function) before = @receiver.send(@msg) #same as calling User.count() callable.call #execute a_function() after = @receiver.send(@msg) #same as calling User.count() before == after end ... ... end -- Posted via http://www.ruby-forum.com/. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.