Carmine Moleti
2008-Sep-22 16:07 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
Hi to everyone, I''m just trying to wrap my mind around BDD and RSpec. I''ve gone through "The rails'' way" and RSpec webcasts series, and still don''t have a clear vision of RSpec. Is there anything else I could read/watch? Back in topic... Now I''m writing specs for a "create" action. The action is written like this: def create authenticate_with_open_id(params[:identity_url], :required => [ :nickname, :email ], :optional => :fullname) do |result, identity_url, registration| if result.successful? @user = User.new @user.identity_url = identity_url @user.roles << Role.find_by_name(''operator'') @user.user_type = ''operator'' assign_registration_attributes!(registration) @user.save(false) redirect_to users_path else redirect_to new_user_path end end end How am I supposed to write a spec to test the code shown above? I tried with: it "should check identity_url via OpenID authentication" do result = mock(''Object'') result.should_receive(:successful?).and_return(:true) @controller.should_receive(:authenticate_with_open_id).with(@identity_url, :required => [ :nickname, :email ], :optional => :fullname).and_return(result) do_verb result.should be_successful end it "should create a new user using OpenID authentication" do @controller.stub!(:authenticate_with_open_id) @user = new_user # thanks to fixture_replacement2 User.stub!(:new).and_return(@user) @user.should_receive(:save).with(false).and_return(true) do_verb end The first test pass successfully while the second throws havocs on me. It complains on the "@user.should_receive(:save..." part. It also complained when I wrote "User.should_receive(:new).and_return(@user)", so I changed it to "User.stub!(:new)...". But I don''t fully understand why I had to do so. Thanks in advance for your help, Carmine -- Posted via http://www.ruby-forum.com/.
Mark Wilden
2008-Sep-22 16:24 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
On Mon, Sep 22, 2008 at 9:07 AM, Carmine Moleti <lists at ruby-forum.com>wrote:> > def create > authenticate_with_open_id(params[:identity_url], :required => [ > :nickname, :email ], :optional => :fullname) do |result, identity_url, > registration| > if result.successful? > @user = User.new > @user.identity_url = identity_url > @user.roles << Role.find_by_name(''operator'') > @user.user_type = ''operator'' > assign_registration_attributes!(registration) > @user.save(false) > redirect_to users_path > else > redirect_to new_user_path > end > end > end > > How am I supposed to write a spec to test the code shown above? >The first thing I would do would be to refactor this code into the User model. The controller shouldn''t be concerned with assigning the user_type, for example. The first test pass successfully while the second throws havocs on me.> It complains on the "@user.should_receive(:save..." part. > It also complained when I wrote > "User.should_receive(:new).and_return(@user)", so I changed > it to "User.stub!(:new)...". But I don''t fully understand why I had to > do so. >Others may have more insight, but I would want to know what the specific complaints were. ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080922/801293d9/attachment.html>
Carmine Moleti
2008-Sep-22 17:40 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
Hi Mark, First off, thanks a lot for your reply! Mark Wilden wrote:> On Mon, Sep 22, 2008 at 9:07 AM, Carmine Moleti > <lists at ruby-forum.com>wrote: > >> assign_registration_attributes!(registration) >> @user.save(false) >> redirect_to users_path >> else >> redirect_to new_user_path >> end >> end >> end >>> The first thing I would do would be to refactor this code into the User > model. The controller shouldn''t be concerned with assigning the > user_type, > for example.Care to explain a little more? I left the code in the controller because it''s the controller that knows about the context in which the gets created. Should I have written one or more class methods on User to accomplish the task of setting properties properly? "user_type" is something I forgot to remove, and it has been replaced by roles.> Others may have more insight, but I would want to know what the specific > complaints were.The error shown is: Mock ''User'' expected :save with (false) once, but received it 0 times -- Posted via http://www.ruby-forum.com/.
Mark Wilden
2008-Sep-22 18:18 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
On Mon, Sep 22, 2008 at 9:07 AM, Carmine Moleti <lists at ruby-forum.com>wrote:> > def create > authenticate_with_open_id(params[:identity_url], :required => [ > :nickname, :email ], :optional => :fullname) do |result, identity_url, > registration| > if result.successful? > @user = User.new > @user.identity_url = identity_url > @user.roles << Role.find_by_name(''operator'') > @user.user_type = ''operator'' > assign_registration_attributes!(registration) > @user.save(false) > redirect_to users_path > else > redirect_to new_user_path > end > end > end >In general, the mantra is "skinny controllers, fat models," meaning that if you can put code in the model, you probably should. For no other reason, because it makes testing easier and faster. Controllers should (again, in general) act as dispatchers to the code that really does the work: the models to handle the logic, and the views to handle the presentation. Searching on the mantra above will probably explain this more fully. In this case, it appears to me that you''re creating a new user based on some OpenID information. There''s nothing in that code that''s specific to POSTing certain information to a certain URL in your app, which is what controllers handle. How a User gets created from OpenID information is really the User class''s responsibility, in my opinion. It''s business logic, and you like to keep business logic as close as possible to its object. While we don''t generally like to program for the future, it''s easy to see that if business logic is spread over controllers as well as models, it''s going to be harder to debug, maintain, etc. it "should create a new user using OpenID authentication" do> @controller.stub!(:authenticate_with_open_id) > @user = new_user # thanks to fixture_replacement2 > User.stub!(:new).and_return(@user) > @user.should_receive(:save).with(false).and_return(true) > do_verb > end > > The first test pass successfully while the second throws havocs on me. > It complains on the "@user.should_receive(:save..." part. > It also complained when I wrote > "User.should_receive(:new).and_return(@user)", so I changed > it to "User.stub!(:new)...". But I don''t fully understand why I had to > do so. >It looks like you want to use something like @controller.should_receive(:authenticate_with_open_id).and_yield(<etc>) I didn''t know about and_yield() before. Cool! ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080922/ded3a188/attachment-0001.html>
Carmine Moleti
2008-Sep-22 18:26 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
[...huge cut...]> In general, the mantra is "skinny controllers, fat models," meaning thatIndeed, while waitin for a reply, I googled a bit and came right to the "skinny controllers, fat models" post! So I''ve refactored the user creation code into a class method on User. User.create_with_role(:role, identity_url, registration) Thus cleaning a bit the whole code. Thanks!> It looks like you want to use something like > @controller.should_receive(:authenticate_with_open_id).and_yield(<etc>) > > I didn''t know about and_yield() before. Cool!I will surely have a look at the "and_yield()", thanks! Again, thanks for your precious help and time! -- Posted via http://www.ruby-forum.com/.
Carmine Moleti
2008-Sep-22 21:34 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
So, after spending a little time trying to work with "and_yield", I strongly believe that: - Either I haven''t understood what "and_yield" does - And this is more than possible - Or "and_yield" is not the toy I should be playing with. What I''m trying to accomplish is to writing tests for the following code (refactored after great advices from ///ark): def create authenticate_with_open_id(params[:identity_url], :required => [ :nickname, :email ], :optional => :fullname) do |result, identity_url, registration| if result.successful? @user = User.create_with_role(:operator, identity_url, registration) redirect_to users_path else redirect_to new_user_path end end end I believe I should test that "authenticate_with_open_id" gets called on @controller, and that "create_with_role" gets called on "User" class. So I wrote this: def do_verb(options = { :identity_url => @identity_url }) post :create, options end it "should create a new user using OpenID authentication" do @user = create_operator_user @controller.should_receive(:authenticate_with_open_id) User.should_receive(:create_with_role).and_return(@user) do_verb response.should redirect_to(users_path) end The test works up to the point where I set expectations on ":authenticate_with_open_id", then it fails with: Mock ''Class'' expected :create_with_role with (any args) once, but received it 0 times I tried this: @controller.should_receive(:authenticate_with_open_id).and_yield do |a,b,c| User.should_receive(:create_with_role).and_return(@user) end and this: @controller.should_receive(:authenticate_with_open_id).and_yield User.should_receive(:create_with_role).and_return(@user) Both leading to the following error: Mock ''UsersController'' yielded || to block with arity of 3 Where am I doing wrong? Thanks in advance for your help, Carmine that really can''t wrap his mind around RSpec :/ -- Posted via http://www.ruby-forum.com/.
Mark Wilden
2008-Sep-22 21:56 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
Carmine, someone could just give you the answer, but pardon me if I get a little Socratic on your ass* Given this code (which is similar to the code you''re testing): authenticate() do |result| puts result end do you understand what''s going on? When is ''puts result'' executed? What code causes it to be executed? Where does ''result'' get its value? What does the ''yield'' keyword do? If you wanted to replace authenticate() with a stub for testing, how would ''result'' get its value? Do you understand the purpose of and_return in RSpec mocking? What would and_yield then do? If you''re unclear on any of these points, take a step back and refresh your memory on the subject of blocks and methods that take blocks (like authenticate_with_open_id()) . You''ll be glad you did! ///ark *Pulp Fiction reference, in case someone reading this thinks I''m merely being obnoxious. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080922/6259a32d/attachment.html>
Carmine Moleti
2008-Sep-23 07:36 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
Mark Wilden wrote:> Carmine, someone could just give you the answer, but pardon me if I get > a > little Socratic on your ass*I do appreciate the fact you''re not going to give me the answer, but the pointers. And, at the risk of appearing dumber than what I am, I''ll try to reply.> Given this code (which is similar to the code you''re testing): > > authenticate() do |result| > puts result > end > > do you understand what''s going on? When is ''puts result'' executed? What > code > causes it to be executed? Where does ''result'' get its value? What does > the > ''yield'' keyword do? If you wanted to replace authenticate() with a stub > forWhen authenticate gets called, it will call the associated block passing "result" to it, so that the code within the block can do its operations over "result" (and perhaps return something to the caller).> testing, how would ''result'' get its value? Do you understand the purpose > of > and_return in RSpec mocking? What would and_yield then do? > > If you''re unclear on any of these points, take a step back and refresh > your > memory on the subject of blocks and methods that take blocks (like > authenticate_with_open_id()) . You''ll be glad you did!I did re-read the topic of blocks in ruby (Programming Ruby 2nd Ed., page 47-49) which, taking into account my bad memory, can''t do any harm. What I don''t have very clear is the whole RSpec way of working and, yes, I''ve watched the Peepcode''s RSpec series many times and still have this feeling. Now, the "and_return" is about the "return value of the mock", while the "and_yield" is about "passing something to the associated block". Right? Having said that, Carmine.expects(:something).like_that(:to_work): @user = create_operator_user result = mock(''Object'') # result.should_receive(:successful?).and_return(:true) registration = %w( a b c ) @controller.should_receive(:authenticate_with_open_id).and_yield(result, at identity_url,registration) do |a_result,an_identity,a_registration| puts "Hello world" --->>A a_result.should_receive(:successful?).and_return(:true) --->>B User.should_receive(:create_with_role).and_return(@user) response.should redirect_to(users_path) end do_verb Which doesn''t work at all. The complaints are about the row marked with: --->>A: Mock ''Object'' received unexpected message :successful? with (no args) if I remove the # on "result.should..." this error disappears, but I get the one at "--->>B" which I guess is for the very same reason of the first one. It seems to me that the code in the block never gets called, indeed I prooved it by placing a "puts" statement in it whose message doesn''t get printed. Now, It would be nice if you could give me pointers to RSpec docs/examples suitable for dumbs like me.> *Pulp Fiction reference, in case someone reading this thinks I''m merely > being obnoxious.Great movie. I really thank you for your patience and time. -- Posted via http://www.ruby-forum.com/.
CarmineM
2008-Sep-23 10:39 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
I came up with this code: def mock_result(stubs = {}) @result ||= mock(''Object'', stubs) end describe "being an administrator" do it "should create a new user using OpenID authentication" do @controller.should_receive(:authenticate_with_open_id).with(@identity_url, anything()).and_yield(mock_result(:successful? => true), @identity_url, mock_registration) User.should_receive(:create_with_role).and_return(@user create_operator_user) do_verb response.should redirect_to(users_path) end end which doesn''t give any error and seems pretty fine to me. Is this the way it is meant to be done? Thanks for your help
Mark Wilden
2008-Sep-23 16:38 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
On Tue, Sep 23, 2008 at 3:39 AM, CarmineM <carmine.moleti at gmail.com> wrote:> I came up with this code: > > def mock_result(stubs = {}) > @result ||= mock(''Object'', stubs) > end > > describe "being an administrator" do > it "should create a new user using OpenID authentication" do > > @controller.should_receive(:authenticate_with_open_id).with(@identity_url, > anything()).and_yield(mock_result(:successful? => true), > @identity_url, mock_registration) > User.should_receive(:create_with_role).and_return(@user > create_operator_user) > do_verb > response.should redirect_to(users_path) > end > end > > which doesn''t give any error and seems pretty fine to me. Is this the > way it is meant to be done? >Without knowing everything about your code, that looks almost there! You''re basically specing that when the create action is called (from do_verb): 1. authenticate_with_open_id should get called with certain parameters 2. When it does get called, have it yield certain other parameters to its block 3. User.create_with_role gets called and returns something returned by create_operator_user I would ask: 1. What arguments should User.create_with_role be called with? These should be specified (tested). 2. What is the return value of User.create_with_role, and what should be done with it? Answer: it returns a user, which should end up getting assigned to the controller''s @user. This specification is accomplished with assigns[:user].should == <the mock user object you''re returning from User.create_with_role>. As far as "the way" to use RSpec in situations like this, I use TDD/BDD. Basically, this means that we don''t write any code without a failing test. Think about that. It''s a pretty weird concept. But it works. One way to achieve this ex post facto would be to comment out all your controller code. Write the simplest spec that describes something you want to have happen. It will fail. Then comment back in the code (and just the code) to make the test pass. After it passes, see if the commented in code expresses its intent well (in other words, clean it up if necessary). Then think of another test (or "description of behavior") to write. It fails, you make it pass, you clean it up. And so on. Continue this cycle until you can''t think of any more tests - in other words, everything you want the code to do has been described/verified/tested. This has several benefits: 1) You take tiny steps, which are so much easier than any other kind of steps. 2) You make sure you haven''t missed any specifications, because every line of code is described before it''s even been written, and 3) You move away from thinking "is the code I just wrote correct?" to "what should this code I''m about to write actually accomplish"? Also, try to match RSpec''s grammar with regard to the "describe" and "it" blocks, such that they stand alone as documentation (though I can never remember the RSpec command to produce it). In the above spec, you''re saying "being an administrator, it should create a new user using OpenID authentication." In reality, what you want to describe is what happens when someone POSTs data to your create action. That may have further context applied, such as "when the poster is an administrator". What line of code does all this describe? The do_verb call. So if I were writing this from scratch, that''s the first line of code I''d write in the spec (presumably, something like ''post :create, :identity_url => ''asdf''''). I''d run this line of code and it would fail (I think) because that action doesn''t exist in the controller (because we commented it out). So the next step would be to do the simplest thing that could make the test pass; obviously, defining the controller method (without anything in it). What should the next test be? Well, you could test that authenticate_with_open_id is called. So you''d set up an expectation on that method: @controller.should_receive(authenticate_with_open_id). You''d run the test, it would fail, and then you''d add the call. Now, what parameters should authenticate_with_open_id receive? Specify them. Test fails. Add them. Test passes. If you go through this process with every single line of code you write, not only do the tests become obvious as you go along, but so does the code under test. You might find that (as in your original code), you have to set up a lot of tests on what happens to the new user. As a result, you might realize that the controller shouldn''t care about these details, and that you should write User.create_with_role (for which you''ll eventually write its own tests). This is an example of how when testing is tedious or difficult, it may point to the code not being structured correctly. Good lord; listen to me rattle on. :) I''m pretty confident in everything I''ve said here, but it''s been explained a lot better by others. Perhaps the best way to get more information on how to use RSpec is to google TDD and/or BDD. And stay on this list, paying especially attention to posts by people like David and Pat (as well as others). Good luck! ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080923/eb8bfea4/attachment.html>
CarmineM
2008-Sep-23 17:27 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
Mark, if by any chance you should ever be in Italy (near by Naples), please drop me a line I owe you a lot more than a beer, but that would do for a start :). Thanks so much for every word and every second spent in helping me! I had to write tests after some code has been written, because I''m struggling with my bosses to let them understand how can BDD improve the way we work. Thanks again!
Mark Wilden
2008-Sep-23 17:37 UTC
[rspec-users] [newbie]: How to test for operations inside a block?
On Tue, Sep 23, 2008 at 10:27 AM, CarmineM <carmine.moleti at gmail.com> wrote:> Mark, if by any chance you should ever be in Italy (near by Naples), > please drop me a line >Ooo, that''s a deal! My son''s in his third year of Italian, and I just made my first ragu Bolognese. :) ///ark -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20080923/0b3d8ae9/attachment.html>