[rspec-users] Factories vs. stubs/mocks

Justin Ko jko170 at gmail.com
Mon Aug 30 14:24:57 EDT 2010



On Aug 30, 1:32 pm, Justin Ko <jko... at gmail.com> wrote:
> 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.
> _______________________________________________
> rspec-users mailing list
> rspec-us... at 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 at 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 at 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 :)


More information about the rspec-users mailing list