Factories vs. stubs/mocks

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 B.
Software Researcher
Dundalk Institute of Technology
[email protected]
Ph.D. Researcher & Composer - Sonic Arts Research Centre
Queen’s University, Belfast
[email protected] / [email protected]

On Aug 30, 11:59 am, Brennon B. [email protected] wrote:

it “should provide a collection of widgets in @widgets” do
Software Researcher
Dundalk Institute of Technology
[email protected]
Ph.D. Researcher & Composer - Sonic Arts Research Centre
Queen’s University, Belfast
[email protected] / [email protected]


rspec-users mailing list
[email protected]://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 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 Aug 30, 12:54 pm, Brennon B. [email protected] wrote:

  get :index

[email protected]
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
[email protected]://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 :slight_smile:

On Aug 30, 2010, at 12:54 PM, Brennon B. 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 B.
[email protected] http://AgileConsultingLLC.com/
[email protected] http://GaslightSoftware.com/

On Aug 30, 1:09 pm, Rob B. [email protected]
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
    [email protected]://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 Aug 30, 1:32 pm, Justin Ko [email protected] wrote:

On Aug 30, 11:59 am, Brennon B. [email protected] 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])
[email protected] 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
removed_email_add[email protected]://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 =>
[email protected]’).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(‘[email protected]’)

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 :slight_smile:

On Mon, Aug 30, 2010 at 14:09, Rob B.

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.

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.
[email protected] 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
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

cheers,
Matt

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

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs