Frank Lakatos
2010-Feb-03 00:00 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
Hi guys, been following for about 3 weeks, first question - I''ve been spending the last couple of months learning RSpec and Cucumber and I''m just finally starting to see the "big picture", at least I think I am. But I''ve got some questions I was hoping you guys can clear up. I''m sure this has been asked a lot, so please bare with me. Just let me know when my logic is wrong or when I''m way off course. You guys have been a kind group, so I''m not to concerned with sounding foolish. I''ve been using .should_receive and .stub to let my spec know that my controller is going to be making a method call. I know .should_receive will fail when you don''t call that method (or if you call it more times than you said you would) and I use stub when It''s going to get called an unpredictable amount of times, like in a before block. To me, it sounds like I don''t really know what the difference is, and I just have some understanding of there symptoms. When i need to talk about a model, I usually use mock_model. I have Factory_girl, but I''ve heard that you should be careful when using Factory girl, as it ties you down to the requirements of the attributes, and not the overall functionality (although, that statement may not be true in itself). So normally, I use Factories in Cucumber, as that is concerned with more higher level usage features, and mock_models for controller/model specs. Alot of times, however, I feel like I''m doing way to much work to get the whole thing working. Maybe I am, maybe I''m not, only way is to show off what I''m doing. app/controller/projects_controller#create def create @client = current_user.company.clients.find(params[:project] [:client_id]) @project = @client.projects.build(params[:project]) if @client.save flash[:notice] = "Added: #{@project.name}" else render :new end end spec/controller/projects_controller_spec describe ProjectsController do describe "POST ''create''" do before do @current_user = mock_model(User) controller.stub(:current_user).and_return @current_user @company = mock_model(Company) @current_user.should_receive(:company).and_return @company @clients = mock("Client List") @company.should_receive(:clients).and_return @clients end describe "when client is found" do before do @client = mock_model(Client) @clients.should_receive(:find).and_return @client end describe "on successful save" do before do @projects = mock_model(ActiveRecord) @client.should_receive(:projects).and_return @projects @project = mock_model(Project) @projects.should_receive(:build).and_return @project @client.should_receive(:save).and_return true @project.should_receive(:name).and_return "New Project" end it "should set up the flash" do post "create", {:project => {:client_id => 1}} flash[:notice].should_not be_nil end end end end end Let me know how it sounds, and it looks like I''m doing so far Thanks, Frank
Andrei Erdoss
2010-Feb-03 05:53 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
Hello Frank,>From my understanding these are the roles of should_receive and stub.should_receive checks to make sure that a method or a property is called. To this you can specify the arguments that it gets called (.with()), what it returns (.and_return) and how many times this happens (.once, .twice etc). stub on the other hand is a place holder for functions calls that have been tested already or are Rails defaults, which don''t need to be tested. stubs are used in conjunction with mock_models, in order to provide for the functions or properties that are needed for the code to run, up to the test point. As far as mock_model and Factory_girl, I can say that mock_models are a much more lightweight structure than Factory_girl object. When testing controllers I try to use only mock_models. As far as models, I use Factory_girl only when dealing with the object being tested. The other complementary objects are mocked by using mock_model. On Wed, Feb 3, 2010 at 2:00 AM, Frank Lakatos <me at franklakatos.com> wrote:> Hi guys, been following for about 3 weeks, first question - > > I''ve been spending the last couple of months learning RSpec and Cucumber > and I''m just finally starting to see the "big picture", at least I think I > am. But I''ve got some questions I was hoping you guys can clear up. I''m sure > this has been asked a lot, so please bare with me. Just let me know when my > logic is wrong or when I''m way off course. You guys have been a kind group, > so I''m not to concerned with sounding foolish. > > I''ve been using .should_receive and .stub to let my spec know that my > controller is going to be making a method call. I know .should_receive will > fail when you don''t call that method (or if you call it more times than you > said you would) and I use stub when It''s going to get called an > unpredictable amount of times, like in a before block. To me, it sounds like > I don''t really know what the difference is, and I just have some > understanding of there symptoms. > > When i need to talk about a model, I usually use mock_model. I have > Factory_girl, but I''ve heard that you should be careful when using Factory > girl, as it ties you down to the requirements of the attributes, and not the > overall functionality (although, that statement may not be true in itself). > So normally, I use Factories in Cucumber, as that is concerned with more > higher level usage features, and mock_models for controller/model specs. > > Alot of times, however, I feel like I''m doing way to much work to get the > whole thing working. Maybe I am, maybe I''m not, only way is to show off what > I''m doing. > > app/controller/projects_controller#create > > def create > @client > current_user.company.clients.find(params[:project][:client_id]) > @project = @client.projects.build(params[:project]) > if @client.save > flash[:notice] = "Added: #{@project.name}" > else > render :new > end > end > > > spec/controller/projects_controller_spec > > describe ProjectsController do > describe "POST ''create''" do > > before do > @current_user = mock_model(User) > controller.stub(:current_user).and_return @current_user > @company = mock_model(Company) > @current_user.should_receive(:company).and_return @company > @clients = mock("Client List") > @company.should_receive(:clients).and_return @clients > end > > describe "when client is found" do > > before do > @client = mock_model(Client) > @clients.should_receive(:find).and_return @client > end > > describe "on successful save" do > > before do > @projects = mock_model(ActiveRecord) > @client.should_receive(:projects).and_return @projects > @project = mock_model(Project) > @projects.should_receive(:build).and_return @project > @client.should_receive(:save).and_return true > @project.should_receive(:name).and_return "New Project" > end > > it "should set up the flash" do > post "create", {:project => {:client_id => 1}} > flash[:notice].should_not be_nil > end > > end > > end > > end > > > end > > > Let me know how it sounds, and it looks like I''m doing so far > > Thanks, > Frank > > > > > > > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Andrei Erdoss -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20100203/623e88be/attachment-0001.html>
Adam Sroka
2010-Feb-03 06:06 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote:> Hello Frank, > > From my understanding these are the roles of should_receive and stub. > > should_receive checks to make sure that a method or a property is called. To > this you can specify the arguments that it gets called (.with()), what it > returns (.and_return) and how many times this happens (.once, .twice etc). > > stub on the other hand is a place holder for functions calls that have been > tested already or are Rails defaults, which don''t need to be tested. stubs > are used in conjunction with mock_models, in order to provide for the > functions or properties that are needed for the code to run, up to the test > point. >I think that it is best to think of these in terms of command query separation. In case you aren''t familiar with that principle, it states that some methods are commands - they tell an object to do something but don''t return anything interesting, and other methods are queries - they return some interesting value but have no side effects. should_receive is how we set an expectation for a command. We don''t really care what a command returns but we do care that it gets called. should_receive literally says that the command should be called with the given parameters. stub is how we handle a query. We care what a query returns, or rather the code we are testing does, but we don''t really care when it gets called (or how often) per se. If we depend on its result then it should be called, but the effect that the result has on the system we''re testing is what we really care about.
J. B. Rainsberger
2010-Feb-03 11:35 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On Tue, Feb 2, 2010 at 19:00, Frank Lakatos <me at franklakatos.com> wrote:> Hi guys, been following for about 3 weeks, first question -This might help a little: http://bit.ly/ONpXE To bring things back to Rails, I use mock_model whenever I want to design controller behavior without relying on the underlying model behavior. I tend to start using mock_model and mostly stubbing model behavior, then as controller behavior begins to reveal itself as model behavior, I push that into the model and mock those methods more frequently. I find this rule of thumb helpful: stub unless you''re certain to want to verify this time that the client invoke the server correctly, and never, never mock multiple methods at once. If you want to mock multiple methods, you probably have too complex an interaction.> app/controller/projects_controller#create > > ?def create > ? ? ?@client > current_user.company.clients.find(params[:project][:client_id]) > ? ? ?@project = @client.projects.build(params[:project]) > ? ? ?if @client.save > ? ? ? ?flash[:notice] = "Added: #{@project.name}" > ? ? ?else > ? ? ? ?render :new > ? ? ?end > ?end > > spec/controller/projects_controller_spec > > describe ProjectsController do > ? ?describe "POST ''create''" do > > ? ?before do > ? ? ?@current_user = mock_model(User) > ? ? ?controller.stub(:current_user).and_return @current_user > ? ? ?@company = mock_model(Company) > ? ? ?@current_user.should_receive(:company).and_return @company > ? ? ?@clients = mock("Client List") > ? ? ? ?@company.should_receive(:clients).and_return @clients > ? ?end > > ? ?describe "when client is found" do > > ? ? ?before do > ? ? ? ?@client = mock_model(Client) > ? ? ? ?@clients.should_receive(:find).and_return @client > ? ? ?end > > ? ? ?describe "on successful save" do > > ? ? ? ?before do > ? ? ? ? ?@projects = mock_model(ActiveRecord) > ? ? ? ? ?@client.should_receive(:projects).and_return @projects > ? ? ? ? ?@project = mock_model(Project) > ? ? ? ? ?@projects.should_receive(:build).and_return @project > ? ? ? ? ?@client.should_receive(:save).and_return true > ? ? ? ? ?@project.should_receive(:name).and_return "New Project" > ? ? ? ?end > > ? ? ? ?it "should set up the flash" do > ? ? ? ? ?post "create", {:project => {:client_id => 1}} > ? ? ? ? ?flash[:notice].should_not be_nil > ? ? ? ?end > > ? ? ? end > > ? ?end > > ?end > > > end > > > Let me know how it sounds, and it looks like I''m doing so farNot bad, but I''d probably extract a method for current_user.company.clients.find(params[:project][:client_id]) and possibly for @client.projects.build(params[:project]) in order to reduce the number of details that have to go into a single spec. def find_client_for_current_user(client_id) current_user.company.clients.find(client_id) end def build_new_project(client, project_attributes) client.projects.build(project_attributes) end def create @client = find_client_for_current_user(params[:project][:client_id]) @project = build_new_project(@client, params[:project]) if @client.save flash[:notice] = "Added: #{@project.name}" else render :new end end Now I can write these specs: stub each of the methods in the first three columns... find_client | build_project | save || expected_result valid | valid | true || added project nil | valid | shouldn''t happen || exception (?) valid | fails | shouldn''t happen || exception (?) valid | valid | false || errors; render new and finally: 1. stub :find_client to answer mock_model(Client); controller should receive :build_new_project with the mock model 2. stub :find_client to answer nil; controller should not receive :build_new_project 3. stub :find_client to answer mock_model(Client); mock model should receive :save That''s the initial spec list I''d write. -- J. B. (Joe) Rainsberger :: http://www.jbrains.ca :: http://blog.thecodewhisperer.com Diaspar Software Services :: http://www.diasparsoftware.com Author, JUnit Recipes 2005 Gordon Pask Award for contribution to Agile practice :: Agile 2010: Learn. Practice. Explore.
Matt Wynne
2010-Feb-03 14:18 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On 3 Feb 2010, at 11:35, J. B. Rainsberger wrote:> I find this rule of thumb helpful: stub unless you''re certain to want > to verify this time that the client invoke the server correctly, and > never, never mock multiple methods at once.Right, because the mock (should_receive) is an assertion, and it''s usually better to have one assertion per example. cheers, Matt http://mattwynne.net +447974 430184
David Chelimsky
2010-Feb-03 14:32 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On Wed, Feb 3, 2010 at 8:18 AM, Matt Wynne <matt at mattwynne.net> wrote:> > On 3 Feb 2010, at 11:35, J. B. Rainsberger wrote: > >> I find this rule of thumb helpful: stub unless you''re certain to want >> to verify this time that the client invoke the server correctly, and >> never, never mock multiple methods at once. > > Right, because the mock (should_receive) is an assertion, and it''s usually > better to have one assertion per example.Over here to the east of the pond, we say "expectation." :)> cheers, > Matt > > http://mattwynne.net > +447974 430184 > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
me at franklakatos.com
2010-Feb-03 14:52 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
Ok, so these ideas seem kind of natural to me, which is nice: mock_models being used to mock non-tested models stub for queries and/or well-tested methods, should_receives for commands While reading over Dave Astlels, I kind of got concerned because of something he states that I feel I''m doing in my specs: "When you realize that it''s all about specifying behaviour and not writing tests, your point of view shifts. Suddenly the idea of having a Test class for each of your production classes is ridiculously limiting. And the thought of testing each of your methods with its own test method (in a 1-1 relationship) will be laughable." This is what I am striving for, but being guided simply by rSpec error messages results me in writing specs like this... describe "POST ''create''" do before do @current_user = mock_model(User) controller.stub(:current_user).and_return @current_user @company = mock_model(Company) @current_user.should_receive(:company).and_return @company @clients = mock("Client List") @company.should_receive(:clients).and_return @clients end describe "when client is found" do before do @client = mock_model(Client) @clients.should_receive(:find).and_return @client end describe "on successful save" do before do @projects = mock_model(ActiveRecord) @client.should_receive(:projects).and_return @projects @project = mock_model(Project) @projects.should_receive(:build).and_return @project @client.should_receive(:save).and_return true @project.should_receive(:name).and_return "New Project" end it "should set up the flash" do post "create", {:project => {:client_id => 1}} flash[:notice].should_not be_nil end end end end ... for a controller that looks like this ... def create @client = current_user.company.clients.find(params[:project][:client_id]) @project = @client.projects.build(params[:project]) if @client.save flash[:notice] = "Added: #{@project.name}" else render :new end end Am I doing the 1-1 thing that BDD specifically set out to avoid? Quoting Adam Sroka <adam.sroka at gmail.com>:> On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote: >> Hello Frank, >> >> From my understanding these are the roles of should_receive and stub. >> >> should_receive checks to make sure that a method or a property is called. To >> this you can specify the arguments that it gets called (.with()), what it >> returns (.and_return) and how many times this happens (.once, .twice etc). >> >> stub on the other hand is a place holder for functions calls that have been >> tested already or are Rails defaults, which don''t need to be tested. stubs >> are used in conjunction with mock_models, in order to provide for the >> functions or properties that are needed for the code to run, up to the test >> point. >> > > I think that it is best to think of these in terms of command query > separation. In case you aren''t familiar with that principle, it states > that some methods are commands - they tell an object to do something > but don''t return anything interesting, and other methods are queries - > they return some interesting value but have no side effects. > > should_receive is how we set an expectation for a command. We don''t > really care what a command returns but we do care that it gets called. > should_receive literally says that the command should be called with > the given parameters. > > stub is how we handle a query. We care what a query returns, or rather > the code we are testing does, but we don''t really care when it gets > called (or how often) per se. If we depend on its result then it > should be called, but the effect that the result has on the system > we''re testing is what we really care about. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
David Chelimsky
2010-Feb-03 15:44 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On Wed, Feb 3, 2010 at 8:52 AM, <me at franklakatos.com> wrote:> Ok, so these ideas seem kind of natural to me, which is nice: > > mock_models being used to mock non-tested models > stub for queries and/or well-tested methods, should_receives for commands > > While reading over Dave Astlels, I kind of got concerned because of > something he states that I feel I''m doing in my specs: > > "When you realize that it''s all about specifying behaviour and not writing > tests, your point of view shifts. Suddenly the idea of having a Test class > for each of your production classes is ridiculously limiting. And the > thought of testing each of your methods with its own test method (in a > 1-1 relationship) will be laughable." > > This is what I am striving for, but being guided simply by rSpec error > messages results me in writing specs like this... > > describe "POST ''create''" do > > ? ?before do > ? ? ?@current_user = mock_model(User) > ? ? ?controller.stub(:current_user).and_return @current_user > ? ? ?@company = mock_model(Company) > ? ? ?@current_user.should_receive(:company).and_return @company > ? ? ?@clients = mock("Client List") > ? ? ?@company.should_receive(:clients).and_return @clients > ? ?end > > ? ?describe "when client is found" do > > ? ? ?before do > ? ? ? ?@client = mock_model(Client) > ? ? ? ?@clients.should_receive(:find).and_return @client > ? ? ?end > > ? ? ?describe "on successful save" do > > ? ? ? ?before do > ? ? ? ? ?@projects = mock_model(ActiveRecord)This is a little odd. @projects is a collection, not a an instance, and mocking ActiveRecord explicitly seems a bit odd. I''d generally us a simple array: @projects = []> ? ? ? ? ?@client.should_receive(:projects).and_return @projects > ? ? ? ? ?@project = mock_model(Project) > ? ? ? ? ?@projects.should_receive(:build).and_return @project > ? ? ? ? ?@client.should_receive(:save).and_return true > ? ? ? ? ?@project.should_receive(:name).and_return "New Project" > ? ? ? ?end > > ? ? ? ?it "should set up the flash" do > ? ? ? ? ?post "create", {:project => {:client_id => 1}} > ? ? ? ? ?flash[:notice].should_not be_nil > ? ? ? ?end > > ? ? ?end > > ? ?end > > > ?end > > > > ... for a controller that looks like this ... > > > def create > ? ? ?@client > current_user.company.clients.find(params[:project][:client_id]) > ? ? ?@project = @client.projects.build(params[:project]) > ? ? ?if @client.save > ? ? ? ?flash[:notice] = "Added: #{@project.name}" > ? ? ?else > ? ? ? ?render :new > ? ? ?end > ?end > > > > Am I doing the 1-1 thing that BDD specifically set out to avoid?1-1 example per method is probably a red flag, but 1-1 spec file per implementation file makes navigation easier, so I think it''s actually a good thing. The underlying problem with 1-1 mappings stems from IDE''s that will make an empty test case by reflecting on an untested object. You''d end up with 50 line long test methods named "testGetName" that actually contain 20 different tests in the one method. That''s an extreme, but I used to see that sort of thing all the time when I was consulting, and it makes it very difficult to understand what is being tested and what went wrong when there is a failure. Make sense? - David> > > > > > > > > > Quoting Adam Sroka <adam.sroka at gmail.com>: > >> On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote: >>> >>> Hello Frank, >>> >>> From my understanding these are the roles of should_receive and stub. >>> >>> should_receive checks to make sure that a method or a property is called. >>> To >>> this you can specify the arguments that it gets called (.with()), what it >>> returns (.and_return) and how many times this happens (.once, .twice >>> etc). >>> >>> stub on the other hand is a place holder for functions calls that have >>> been >>> tested already or are Rails defaults, which don''t need to be tested. >>> stubs >>> are used in conjunction with mock_models, in order to provide for the >>> functions or properties that are needed for the code to run, up to the >>> test >>> point. >>> >> >> I think that it is best to think of these in terms of command query >> separation. In case you aren''t familiar with that principle, it states >> that some methods are commands - they tell an object to do something >> but don''t return anything interesting, and other methods are queries - >> they return some interesting value but have no side effects. >> >> should_receive is how we set an expectation for a command. We don''t >> really care what a command returns but we do care that it gets called. >> should_receive literally says that the command should be called with >> the given parameters. >> >> stub is how we handle a query. We care what a query returns, or rather >> the code we are testing does, but we don''t really care when it gets >> called (or how often) per se. If we depend on its result then it >> should be called, but the effect that the result has on the system >> we''re testing is what we really care about. >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
me at franklakatos.com
2010-Feb-03 16:07 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
To say thank you for all your constuctive feedback would not be enough; all these insights are really helping me to get the provebial "it". Dave, I completely agree that the mock_model(ActiveRecord) was bizzare, but my specs kept failing because @projects was to receive .build, and it complained it didn''t know about it. Using AR fixed that, but that must have been from a while ago, because I put [] back in it''s place (which i had before) and the spec passes. I absolutely love the idea of encapsulated the daisy chained calls (c_u.comp.project) into a controller methods so all i gotta do is stub that out. So that makes me wonder -- as of right now, I write this long spec, which looks way more involved than the controller itself. And, I''d be lying if I said that I thought about my spec first before writing the app code. Is the idea that I should be trying to think of a simpler spec, which will force a simpler controller (like encapsulating functionality into more controller methods) Once again, thanks++ Frank Quoting David Chelimsky <dchelimsky at gmail.com>:> On Wed, Feb 3, 2010 at 8:52 AM, <me at franklakatos.com> wrote: >> Ok, so these ideas seem kind of natural to me, which is nice: >> >> mock_models being used to mock non-tested models >> stub for queries and/or well-tested methods, should_receives for commands >> >> While reading over Dave Astlels, I kind of got concerned because of >> something he states that I feel I''m doing in my specs: >> >> "When you realize that it''s all about specifying behaviour and not writing >> tests, your point of view shifts. Suddenly the idea of having a Test class >> for each of your production classes is ridiculously limiting. And the >> thought of testing each of your methods with its own test method (in a >> 1-1 relationship) will be laughable." >> >> This is what I am striving for, but being guided simply by rSpec error >> messages results me in writing specs like this... >> >> describe "POST ''create''" do >> >> ? ?before do >> ? ? ?@current_user = mock_model(User) >> ? ? ?controller.stub(:current_user).and_return @current_user >> ? ? ?@company = mock_model(Company) >> ? ? ?@current_user.should_receive(:company).and_return @company >> ? ? ?@clients = mock("Client List") >> ? ? ?@company.should_receive(:clients).and_return @clients >> ? ?end >> >> ? ?describe "when client is found" do >> >> ? ? ?before do >> ? ? ? ?@client = mock_model(Client) >> ? ? ? ?@clients.should_receive(:find).and_return @client >> ? ? ?end >> >> ? ? ?describe "on successful save" do >> >> ? ? ? ?before do >> ? ? ? ? ?@projects = mock_model(ActiveRecord) > > This is a little odd. @projects is a collection, not a an instance, > and mocking ActiveRecord explicitly seems a bit odd. I''d generally us > a simple array: > > @projects = [] > >> ? ? ? ? ?@client.should_receive(:projects).and_return @projects >> ? ? ? ? ?@project = mock_model(Project) >> ? ? ? ? ?@projects.should_receive(:build).and_return @project >> ? ? ? ? ?@client.should_receive(:save).and_return true >> ? ? ? ? ?@project.should_receive(:name).and_return "New Project" >> ? ? ? ?end >> >> ? ? ? ?it "should set up the flash" do >> ? ? ? ? ?post "create", {:project => {:client_id => 1}} >> ? ? ? ? ?flash[:notice].should_not be_nil >> ? ? ? ?end >> >> ? ? ?end >> >> ? ?end >> >> >> ?end >> >> >> >> ... for a controller that looks like this ... >> >> >> def create >> ? ? ?@client >> current_user.company.clients.find(params[:project][:client_id]) >> ? ? ?@project = @client.projects.build(params[:project]) >> ? ? ?if @client.save >> ? ? ? ?flash[:notice] = "Added: #{@project.name}" >> ? ? ?else >> ? ? ? ?render :new >> ? ? ?end >> ?end >> >> >> >> Am I doing the 1-1 thing that BDD specifically set out to avoid? > > 1-1 example per method is probably a red flag, but 1-1 spec file per > implementation file makes navigation easier, so I think it''s actually > a good thing. > > The underlying problem with 1-1 mappings stems from IDE''s that will > make an empty test case by reflecting on an untested object. You''d end > up with 50 line long test methods named "testGetName" that actually > contain 20 different tests in the one method. That''s an extreme, but I > used to see that sort of thing all the time when I was consulting, and > it makes it very difficult to understand what is being tested and what > went wrong when there is a failure. > > Make sense? > > - David >> >> >> >> >> >> >> >> >> >> Quoting Adam Sroka <adam.sroka at gmail.com>: >> >>> On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote: >>>> >>>> Hello Frank, >>>> >>>> From my understanding these are the roles of should_receive and stub. >>>> >>>> should_receive checks to make sure that a method or a property is called. >>>> To >>>> this you can specify the arguments that it gets called (.with()), what it >>>> returns (.and_return) and how many times this happens (.once, .twice >>>> etc). >>>> >>>> stub on the other hand is a place holder for functions calls that have >>>> been >>>> tested already or are Rails defaults, which don''t need to be tested. >>>> stubs >>>> are used in conjunction with mock_models, in order to provide for the >>>> functions or properties that are needed for the code to run, up to the >>>> test >>>> point. >>>> >>> >>> I think that it is best to think of these in terms of command query >>> separation. In case you aren''t familiar with that principle, it states >>> that some methods are commands - they tell an object to do something >>> but don''t return anything interesting, and other methods are queries - >>> they return some interesting value but have no side effects. >>> >>> should_receive is how we set an expectation for a command. We don''t >>> really care what a command returns but we do care that it gets called. >>> should_receive literally says that the command should be called with >>> the given parameters. >>> >>> stub is how we handle a query. We care what a query returns, or rather >>> the code we are testing does, but we don''t really care when it gets >>> called (or how often) per se. If we depend on its result then it >>> should be called, but the effect that the result has on the system >>> we''re testing is what we really care about. >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-users at rubyforge.org >>> http://rubyforge.org/mailman/listinfo/rspec-users >>> >> >> >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Nicolás Sanguinetti
2010-Feb-03 16:35 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
On Wed, Feb 3, 2010 at 2:07 PM, <me at franklakatos.com> wrote:> I absolutely love the idea of encapsulated the daisy chained calls > (c_u.comp.project) into a controller methods so all i gotta do is stub that > out.Oooh, I hate that one :) You''re adding lots of small methods that actually don''t define how the class should behave, IMO. In fact, how a client or project is related to a user is a responsibility of the models, not the controllers. I would much rather turn @client = current_user.company.clients.find(params[:project][:client_id]) @project = @client.projects.build(params[:project]) into @client = current_user.find_client(params[:project][:client_id]) @project = @client.projects.new(params[:project]) And add the helper method on the models. Even more: @project = current_user.add_project(params[:project]) And let the handling of the client and the business rules ("a project must belong to a client") to the models, as well Then your controller would be a lot slimmer, and the tests for it much simpler. def create @project = current_user.add_project(params[:project]) if @project.save flash[:notice] = "Added ''#{@project.name}''" redirect_to somewhere else render :new end end Cheers, -foca> > Once again, thanks++ > Frank > > > Quoting David Chelimsky <dchelimsky at gmail.com>: > >> On Wed, Feb 3, 2010 at 8:52 AM, ?<me at franklakatos.com> wrote: >>> >>> Ok, so these ideas seem kind of natural to me, which is nice: >>> >>> mock_models being used to mock non-tested models >>> stub for queries and/or well-tested methods, should_receives for commands >>> >>> While reading over Dave Astlels, I kind of got concerned because of >>> something he states that I feel I''m doing in my specs: >>> >>> "When you realize that it''s all about specifying behaviour and not >>> writing >>> tests, your point of view shifts. Suddenly the idea of having a Test >>> class >>> for each of your production classes is ridiculously limiting. And the >>> thought of testing each of your methods with its own test method (in a >>> 1-1 relationship) will be laughable." >>> >>> This is what I am striving for, but being guided simply by rSpec error >>> messages results me in writing specs like this... >>> >>> describe "POST ''create''" do >>> >>> ? ?before do >>> ? ? ?@current_user = mock_model(User) >>> ? ? ?controller.stub(:current_user).and_return @current_user >>> ? ? ?@company = mock_model(Company) >>> ? ? ?@current_user.should_receive(:company).and_return @company >>> ? ? ?@clients = mock("Client List") >>> ? ? ?@company.should_receive(:clients).and_return @clients >>> ? ?end >>> >>> ? ?describe "when client is found" do >>> >>> ? ? ?before do >>> ? ? ? ?@client = mock_model(Client) >>> ? ? ? ?@clients.should_receive(:find).and_return @client >>> ? ? ?end >>> >>> ? ? ?describe "on successful save" do >>> >>> ? ? ? ?before do >>> ? ? ? ? ?@projects = mock_model(ActiveRecord) >> >> This is a little odd. @projects is a collection, not a an instance, >> and mocking ActiveRecord explicitly seems a bit odd. I''d generally us >> a simple array: >> >> @projects = [] >> >>> ? ? ? ? ?@client.should_receive(:projects).and_return @projects >>> ? ? ? ? ?@project = mock_model(Project) >>> ? ? ? ? ?@projects.should_receive(:build).and_return @project >>> ? ? ? ? ?@client.should_receive(:save).and_return true >>> ? ? ? ? ?@project.should_receive(:name).and_return "New Project" >>> ? ? ? ?end >>> >>> ? ? ? ?it "should set up the flash" do >>> ? ? ? ? ?post "create", {:project => {:client_id => 1}} >>> ? ? ? ? ?flash[:notice].should_not be_nil >>> ? ? ? ?end >>> >>> ? ? ?end >>> >>> ? ?end >>> >>> >>> ?end >>> >>> >>> >>> ... for a controller that looks like this ... >>> >>> >>> def create >>> ? ? ?@client >>> current_user.company.clients.find(params[:project][:client_id]) >>> ? ? ?@project = @client.projects.build(params[:project]) >>> ? ? ?if @client.save >>> ? ? ? ?flash[:notice] = "Added: #{@project.name}" >>> ? ? ?else >>> ? ? ? ?render :new >>> ? ? ?end >>> ?end >>> >>> >>> >>> Am I doing the 1-1 thing that BDD specifically set out to avoid? >> >> 1-1 example per method is probably a red flag, but 1-1 spec file per >> implementation file makes navigation easier, so I think it''s actually >> a good thing. >> >> The underlying problem with 1-1 mappings stems from IDE''s that will >> make an empty test case by reflecting on an untested object. You''d end >> up with 50 line long test methods named "testGetName" that actually >> contain 20 different tests in the one method. That''s an extreme, but I >> used to see that sort of thing all the time when I was consulting, and >> it makes it very difficult to understand what is being tested and what >> went wrong when there is a failure. >> >> Make sense? >> >> - David >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> Quoting Adam Sroka <adam.sroka at gmail.com>: >>> >>>> On Tue, Feb 2, 2010 at 9:53 PM, Andrei Erdoss <erdoss at gmail.com> wrote: >>>>> >>>>> Hello Frank, >>>>> >>>>> From my understanding these are the roles of should_receive and stub. >>>>> >>>>> should_receive checks to make sure that a method or a property is >>>>> called. >>>>> To >>>>> this you can specify the arguments that it gets called (.with()), what >>>>> it >>>>> returns (.and_return) and how many times this happens (.once, .twice >>>>> etc). >>>>> >>>>> stub on the other hand is a place holder for functions calls that have >>>>> been >>>>> tested already or are Rails defaults, which don''t need to be tested. >>>>> stubs >>>>> are used in conjunction with mock_models, in order to provide for the >>>>> functions or properties that are needed for the code to run, up to the >>>>> test >>>>> point. >>>>> >>>> >>>> I think that it is best to think of these in terms of command query >>>> separation. In case you aren''t familiar with that principle, it states >>>> that some methods are commands - they tell an object to do something >>>> but don''t return anything interesting, and other methods are queries - >>>> they return some interesting value but have no side effects. >>>> >>>> should_receive is how we set an expectation for a command. We don''t >>>> really care what a command returns but we do care that it gets called. >>>> should_receive literally says that the command should be called with >>>> the given parameters. >>>> >>>> stub is how we handle a query. We care what a query returns, or rather >>>> the code we are testing does, but we don''t really care when it gets >>>> called (or how often) per se. If we depend on its result then it >>>> should be called, but the effect that the result has on the system >>>> we''re testing is what we really care about. >>>> _______________________________________________ >>>> rspec-users mailing list >>>> rspec-users at rubyforge.org >>>> http://rubyforge.org/mailman/listinfo/rspec-users >>>> >>> >>> >>> >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-users at rubyforge.org >>> http://rubyforge.org/mailman/listinfo/rspec-users >>> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
J. B. Rainsberger
2010-Feb-03 17:21 UTC
[rspec-users] When to use Factories, Mock Models, Mocks & Stubs
2010/2/3 Nicol?s Sanguinetti <godfoca at gmail.com>:> On Wed, Feb 3, 2010 at 2:07 PM, ?<me at franklakatos.com> wrote: >> I absolutely love the idea of encapsulated the daisy chained calls >> (c_u.comp.project) into a controller methods so all i gotta do is stub that >> out. > > Oooh, I hate that one :) > > You''re adding lots of small methods that actually don''t define how the > class should behave, IMO. > In fact, how a client or project is related to a user is a > responsibility of the models, not the controllers. > > I would much rather turn > > @client = current_user.company.clients.find(params[:project][:client_id]) > @project = @client.projects.build(params[:project]) > > into > > @client = current_user.find_client(params[:project][:client_id]) > @project = @client.projects.new(params[:project]) > > And add the helper method on the models.I strongly agree. Extracting the methods to the controller is merely an intermediate step. If the methods belong on other objects, then that tends to become clear soon enough. -- J. B. (Joe) Rainsberger :: http://www.jbrains.ca :: http://blog.thecodewhisperer.com Diaspar Software Services :: http://www.diasparsoftware.com Author, JUnit Recipes 2005 Gordon Pask Award for contribution to Agile practice :: Agile 2010: Learn. Practice. Explore.