I am, as usual, assigning an instance variable in a controller's index
action to find all instances of a model in the database. I want to
write a spec that checks that this variable is assigned correctly. I
can do:
it "should provide a collection of widgets in @widgets" do
widget = Widget.create("title" => "my widget")
get :index
assigns[:widgets].should include(widget)
end
or:
it "should provide a collection of widgets in @widgets" do
widget = Factory.create(:widget)
get :index
assigns[:widgets].should include(widget)
end
Is there a better way to do this with a mock model, though?
Thanks,
Brennon Bortz
Software Researcher
Dundalk Institute of Technology
brennon.bortz@casala.ie
Ph.D. Researcher & Composer - Sonic Arts Research Centre
Queen's University, Belfast
brennon@brennonbortz.com / bbortz01@qub.ac.uk
on 2010-08-30 18:00
on 2010-08-30 18:29
On Aug 30, 11:59 am, Brennon Bortz <bren...@brennonbortz.com> wrote: > it "should provide a collection of widgets in @widgets" do > Software Researcher > Dundalk Institute of Technology > brennon.bo...@casala.ie > Ph.D. Researcher & Composer - Sonic Arts Research Centre > Queen's University, Belfast > bren...@brennonbortz.com / bbort...@qub.ac.uk > > _______________________________________________ > rspec-users mailing list > rspec-us...@rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users Currently, what you're doing is checking that the Widget model returns the correct widgets. If you want to isolate your controller spec from the model, you must stub or mock the model method call in the action. Example: it "..." do widget = mock_model(Widget) Widget.should_receive(:all).and_return([widget]) get :index assigns[:widgets].should include(widget) end To check that Widget.all does indeed return the correct widgets, I would spec that behaviour in the Widget model spec. Hope that helps.
on 2010-08-30 19:00
On 30 Aug 2010, at 17:17, Justin Ko wrote: >> >> Thanks, >> rspec-users mailing list > get :index > assigns[:widgets].should include(widget) > end > > To check that Widget.all does indeed return the correct widgets, I > would spec that behaviour in the Widget model spec. > > Hope that helps. Hrm...that's exactly what I'd started with, and I was getting the following error: Failures: 1) WidgetsController GET 'index' should provide a collection of widgets in @widgets Failure/Error: assigns[:widgets].should include(widget) expected [] to include #<Widget:0x81686290 @name="Widget_1001"> Stupidly, I had defined my controller method as: def index @widgets = Widget.find(:all) end Changed that assignment to Widget.all...problem solved.
on 2010-08-30 19:07
On Aug 30, 12:54 pm, Brennon Bortz <bren...@brennonbortz.com> wrote: > > >> get :index > >> brennon.bo...@casala.ie > > the model, you must stub or mock the model method call in the action. > > would spec that behaviour in the Widget model spec. > Stupidly, I had defined my controller method as: > > def index > @widgets = Widget.find(:all) > end > > Changed that assignment to Widget.all...problem solved. > _______________________________________________ > rspec-users mailing list > rspec-us...@rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users To mock a method that receives an argument, you can use the "with" method. Example: Widget.should_receive(:find).with(:all).and_return([widget]) Glad you got things fixed :)
on 2010-08-30 19:10
On Aug 30, 2010, at 12:54 PM, Brennon Bortz wrote: >>> assigns[:widgets].should include(widget) >>> Is there a better way to do this with a mock model, though? >>> >> it "..." do > > > def index > @widgets = Widget.find(:all) > end > > Changed that assignment to Widget.all...problem solved. And THAT is the problem with using mocks (or stubs) for this. You run the very real risk of specifying the implementation details in the structure of the spec/test. There is a problem when you've limited the refactoring that can be done without altering the spec/test. Since Widget.find(:all) and Widget.all should always have the same result, the mere presence of the mock has cut off one possible refactoring. (The one that I distaste most limits changing .find(params[:id]) to .find_by_id(params[:id]) by mocking/stubbing the call to find.) Think about what really needs to be specified and be careful to mock/ stub as little as possible and to do so in a way that will limit opportunities to refactor the least. -Rob Rob Biedenharn Rob@AgileConsultingLLC.com http://AgileConsultingLLC.com/ rab@GaslightSoftware.com http://GaslightSoftware.com/
on 2010-08-30 19:32
On Aug 30, 1:09 pm, Rob Biedenharn <R...@AgileConsultingLLC.com> wrote: > >>> want to write a spec that checks that this variable is assigned > >>> it "should provide a collection of widgets in @widgets" do > >>> Software Researcher > >> Currently, what you're doing is checking that the Widget model > >> end > > 1) WidgetsController GET 'index' should provide a collection of > > Changed that assignment to Widget.all...problem solved. > Think about what really needs to be specified and be careful to mock/ > rspec-users mailing list > rspec-us...@rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users The method you suggested does benefit refactoring. However, by not stubbing or mocking, your controller is no longer in isolation. Which means if Widget.all breaks, the model AND controller spec will fail. The are pros and cons to both ways. But what has pushed me to the isolation side is that without mocking/stubbing, you must create a record in the database (via fixtures or factories) and it is much slower.
on 2010-08-30 20:26
On Aug 30, 1:32 pm, Justin Ko <jko...@gmail.com> wrote: > > >> On Aug 30, 11:59 am, Brennon Bortz <bren...@brennonbortz.com> wrote: > > > >>> Thanks, > > >>> rspec-users mailing list > > >> Widget.should_receive(:all).and_return([widget]) > > > following error: > > > @widgets = Widget.find(:all) > > (The one that I distaste most limits changing .find(params[:id]) > > r...@GaslightSoftware.com http://GaslightSoftware.com/ > isolation side is that without mocking/stubbing, you must create a > record in the database (via fixtures or factories) and it is much > slower. > _______________________________________________ > rspec-users mailing list > rspec-us...@rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users Just remembered another reason why I chose isolation. Sometimes, the setup to NOT isolate can be a real pain. a.k.a creating factories and relationships. But, let's say you've something like this in your controller: User.where(:email => 'test@blah.com').includes(:widgets).order('users.email ASC').limit(3) Simply stubbing or mocking that out would not spec the SQL very well. So what I usually do is extract it to the model: User.widgets_by_email('test@blah.com') # in model def self.widgets_by_email(email) where(:email => email).includes(:widgets).order('users.email ASC').limit(3) end After doing that, I feel comfortable mocking out "User.widgets_by_email" in the controller, because all I care about is if its called or not. And of course, since the method has been moved to the model, you can unit spec the hell out of it :)
on 2010-08-31 11:16
On 30 Aug 2010, at 18:32, Justin Ko wrote: >>> On 30 Aug 2010, at 17:17, Justin Ko wrote: >>>>> end >> >>>>> _______________________________________________ >>>> widget = mock_model(Widget) >>> Hrm...that's exactly what I'd started with, and I was getting the >>> def index >> the mere presence of the mock has cut off one possible refactoring. >> R...@AgileConsultingLLC.com http://AgileConsultingLLC.com/ > The are pros and cons to both ways. But what has pushed me to the > isolation side is that without mocking/stubbing, you must create a > record in the database (via fixtures or factories) and it is much > slower. Personally, I would use this as a driver to create a new method on Widget which did exactly what this controller wants, wrapping the details of the ActiveRecord API. That way, I have a nice, readable controller with fast, isolated, specs; a clear API on my models that makes sense in my domain; and if I want to change from a relational DB to a key-value store, I don't have a dependency on ActiveRecord leaking out of my model all over the place. > _______________________________________________ > rspec-users mailing list > rspec-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users cheers, Matt http://blog.mattwynne.net +44(0)7974 430184
on 2010-09-06 16:39
On Mon, Aug 30, 2010 at 14:09, Rob Biedenharn > And THAT is the problem with using mocks (or stubs) for this. You run the > very real risk of specifying the implementation details in the structure of > the spec/test. There is a problem when you've limited the refactoring that > can be done without altering the spec/test. Since Widget.find(:all) and > Widget.all should always have the same result, the mere presence of the mock > has cut off one possible refactoring. (The one that I distaste most limits > changing .find(params[:id]) to .find_by_id(params[:id]) by mocking/stubbing > the call to find.) On the contrary: by stubbing you've uncovered that you had used a low-level API (#find) instead of a high-level API (#all). This prompted you to make a conscious choice, which you did, and I prefer the choice you made, to invoke #all instead of #find. …and THAT is the magic of using stubs and expectations for this. -- 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.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.