CarmineM
2008-May-10 12:23 UTC
[rspec-users] 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?
Edvard Majakari
2008-May-10 13:34 UTC
[rspec-users] Newbie: lambda do...end.should change(Model, :count).by(1). Doesn''t work
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) > endYou 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
Craig Demyanovich
2008-May-10 13:40 UTC
[rspec-users] Newbie: lambda do...end.should change(Model, :count).by(1). Doesn''t work
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
Rick DeNatale
2008-May-10 14:28 UTC
[rspec-users] Newbie: lambda do...end.should change(Model, :count).by(1). Doesn''t work
On Sat, May 10, 2008 at 9:40 AM, Craig Demyanovich <cdemyanovich at gmail.com> 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#M000386But, 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/
CarmineM
2008-May-11 15:16 UTC
[rspec-users] Newbie: lambda do...end.should change(Model, :count).by(1). Doesn''t work
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 :) ). 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".> 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). >[...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