[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