What does using a lambda in rspec tests accomplish?

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)>'

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 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

David Chelimsky wrote in post #1013001:

@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

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.

Whoops. This: