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: