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