[rspec-users] Factories vs. stubs/mocks

Matt Wynne matt at mattwynne.net
Tue Aug 31 05:01:07 EDT 2010


On 30 Aug 2010, at 18:32, Justin Ko 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.

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 at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

cheers,
Matt

http://blog.mattwynne.net
+44(0)7974 430184



More information about the rspec-users mailing list