Newbie: lambda do...end.should change(Model, :count).by(1). Doesn't work

Hi to everyone,

I’m an RSpec, and BDD in general, newbie so in order to learn I have
chosen to use my personal website as a tesbed.

I’m having difficulties juggling with mocks, and in particula with the
following code.

Here’s the controller action:

def create
@album = Album.new(params[:album])
if @album.save
flash[:notice] = “album saved”
else
flash[:notice] = “could not save album”
end
redirect_to albums_path
end

And here an excerpt of the spec I’m fighting with:

describe “create” do

before(:each) do
  login_as(:admin)
  @album = mock_model(Album, :save => true)
  Album.stub!(:new).and_return(@album)
  @params = { :title => 'a new title', :description => 'a new

description’, :category => ‘a new category’ }
end

def do_verb(params = @params)
  post :create, :album => params
end

it "should change the Albums count by 1 after POST" do
  lambda do
    @album.should_receive(:save).and_return(true)
    do_verb
  end.should change(Album, :count).by(1)
end

end

When I run the spec (in TextMate) I get the following error:

count should have been changed by 1, but was changed by 0

And the row with “lambda do” highlighted.

Where am I doing wrong?

Thanks in advance for your help.

Regards,
Carmine

P.S.:
I already have bought Peepcode’s screencast on RSpec, but they’ve left
me only with a partial picture. Is there anything else I can use to
learn?

Hi,

def do_verb(params = @params)
post :create, :album => params
end

it “should change the Albums count by 1 after POST” do
lambda do
@album.should_receive(:save).and_return(true)
do_verb
end.should change(Album, :count).by(1)
end

You expect the database count to change, but because you mock away the
method :save,
the object won’t get saved. You don’t need to test for Album#count to
increase; if the ‘save’ method is called
for the object, you can rest assured there’s one more object in the
db. And if save doesn’t work as it should, the problem
isn’t in your code but in the ActiveRecord instead.

I’m not sure, but I guess that people who make this kind of mistake
think that mocking/stubbing a method doesn’t
prevent the calling of the original method, but it does.

The whole idea in mocking is to specify expectations related to
behaviour
. The following (simplified) rule could
be stated as follows: if you use mocks, you don’t need to (nor should)
use state-based assertions, as
lambda {…}.should change(Model, :method) does. And vice versa; if
you use state to test stuff, you don’t use mocks. But as
I said, it is an oversimplification. For example, usually you don’t
want to mock or stub methods to unit/thingy under the test, but
you should probably mock/stub methods related to associated classes.

That said, some people consider use of mocks dangerous and brittle to
changes. I don’t - if you have a language terse
enough (like Ruby) and an advanced speccing framework (like RSpec),
changing your tests to reflect intended changes to code
is not a chore too tedious for me. Besides, to ensure at least
mediocre test/spec coverage you should automate your integration tests
as well, where you don’t use mock/stub objects at all (with the
exception of very expensive/non-deterministic resources such as say,
network and random number generators).

As for the other question, I learned BDD by reading the
well-documented RSpec site itself and random blog entries related to
BDD and Rails. But to really learn BDD well, I’d recommend any
newcomer to get acquaintanted with core concepts of BDD first, like
unit testing
and mocks/stubs. For the former I recommend books related to
Test-Driven Development like those by David Astel and Kent Beck, and
for the latter articles related to mocking: Fowler’s Mocks Aren’t
Stubs (http://martinfowler.com/articles/mocksArentStubs.html) is an
excellent explanation (or a viewpoint; some people don’t make such
difference between the two) for both mocks and stubs, and the article
“Mock Roles, not Objects” (http://www.jmock.org/oopsla2004.pdf) is an
invaluable gem in itself – for me it took two reads to really
understand (I hope!) it, though, with more than one year in between
(the catch is that the paper appears to be simple; it doesn’t contain
cryptic formulas or ingenious mathematical proofs, however, I believe
it may take a while before the reader really, really understands the
subject of the article).


“One day, when he was naughty, Mr Bunnsy looked over the hedge into
Farmer Fred’s field and it was full of fresh green lettuces. Mr
Bunnsy, however, was not full of lettuces. This did not seem fair.”
– Terry Pratchett, Mr. Bunnsy Has An Adventure

A syntax note from the RSpec docs: “blocks passed to should change and
should_not change must use the {} form (do/end is not supported).”

http://rspec.info/rdoc/classes/Spec/Matchers.html#M000386

Regards,
Craig

On Sat, May 10, 2008 at 9:40 AM, Craig D.
[email protected] wrote:

A syntax note from the RSpec docs: “blocks passed to should change and
should_not change must use the {} form (do/end is not supported).”

http://rspec.info/rdoc/classes/Spec/Matchers.html#M000386

But, I’m pretty sure that this is talking about a block which is
passed as an argument to should_change/should_not_change as in this
example from the docs:

string = “string”
lambda {
string.reverse
}.should change { string }.from(“string”).to(“gnirts”)

as opposed to:

string = “string”
lambda {
string.reverse
}.should change do
string
end.from(“string”).to(“gnirts”)

This is because do/end binds less tightly than {} so that in the first
(working case) the block is passed to the change method, while in the
second one it gets passed to the should method instead.

On the other hand:

string = “string”
lambda do
string.reverse
end.should change { string }.from(“string”).to(“gnirts”)

should work, although I prefer the {} syntax in this case.

Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

First off, thanks for your help and time and hints!

[…snipped some code…]

You expect the database count to change, but because you mock away the
method :save,
the object won’t get saved. You don’t need to test for Album#count to
increase; if the ‘save’ method is called
for the object, you can rest assured there’s one more object in the
db. And if save doesn’t work as it should, the problem
isn’t in your code but in the ActiveRecord instead.

I’m not sure, but I guess that people who make this kind of mistake
think that mocking/stubbing a method doesn’t
prevent the calling of the original method, but it does.

My fault, the code posted was my last (desperate I dare to say)
attempt to
get the whole thing work.
In the first “version”, it doesn’t have a stub/mock on “save” method,
still it didn’t
worked the way I expected. (Put aside the fact that I shouldn’t be
testing the ActiveRecord’s
code, which is a profound truth :slight_smile: ).

In my case I’ve been fooled by the “should_receive” thing. Being that
an expectation, to me,
only meant that the system was being checked about that particular
event (the call to save) to occur.

I didn’t pay much attention to the fact that
“should_receive(:save).and_…” wasn’t only about
expectation. I thought that after the expectation being met, the
regular “save” method was
to be called.

That’s why I choosed “mock_model” over the regular (and more generic)
“mock”.

That said, some people consider use of mocks dangerous and brittle to
changes. I don’t - if you have a language terse
enough (like Ruby) and an advanced speccing framework (like RSpec),
changing your tests to reflect intended changes to code
is not a chore too tedious for me. Besides, to ensure at least
mediocre test/spec coverage you should automate your integration tests
as well, where you don’t use mock/stub objects at all (with the
exception of very expensive/non-deterministic resources such as say,
network and random number generators).

[…snipped tips on how to learn BDD…]
Thanks for the pointers and tips. I will read first Fowler’s article
and
the other you mentioned, then I’ll try to see if things get better.
BDD is something I can’t “afford” to use at work, and so I’m trying at
home
during “spare time”, using my website as a testbed before attempting
writing a “real” application.

Again, thanks for your help!
Regards,
Carmine

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs