[rspec-users] Factories vs. stubs/mocks

Justin Ko jko170 at gmail.com
Mon Aug 30 13:32:11 EDT 2010



On Aug 30, 1:09 pm, Rob Biedenharn <R... at AgileConsultingLLC.com>
wrote:
> On Aug 30, 2010, at 12:54 PM, Brennon Bortz wrote:
>
>
>
>
>
> > On 30 Aug 2010, at 17:17, Justin Ko wrote:
> >> On Aug 30, 11:59 am, Brennon Bortz <bren... at brennonbortz.com> wrote:
> >>> 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.bo... at casala.ie
> >>> Ph.D. Researcher & Composer - Sonic Arts Research Centre
> >>> Queen's University, Belfast
> >>> bren... at brennonbortz.com / bbort... at qub.ac.uk
>
> >>> _______________________________________________
> >>> rspec-users mailing list
> >>> rspec-us... at 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.
>
> > 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.
>
> 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          
> R... at AgileConsultingLLC.com  http://AgileConsultingLLC.com/
> r... at GaslightSoftware.com            http://GaslightSoftware.com/
>
> _______________________________________________
> rspec-users mailing list
> rspec-us... at 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.


More information about the rspec-users mailing list