[rspec-users] Mocks? Really?

Zach Dennis zach.dennis at gmail.com
Sun Dec 23 17:27:05 EST 2007


I know the questioned are directed towards Dan so I hope you don't me
chiming in. My comments are inline.

On Dec 15, 2007 2:17 AM, Pat Maddox <pergesu at gmail.com> wrote:
>
> On Dec 8, 2007 4:06 AM, Dan North <tastapod at gmail.com> wrote:
> > I prefer the mantra "mock roles, not objects", in other words, mock things
> > that have behaviour (services, components, resources, whatever your
> > preferred term is) rather than stubbing out domain objects themselves. If
> > you have to mock domain objects it's usually a smell that your domain
> > implementation is too tightly coupled to some infrastructure.
>
> Assuming you could easily write Rails specs using the real domain
> objects, but not hit the database, would you "never" mock domain
> objects (where "never" means you deviate only in extraordinary
> circumstances)?  I'm mostly curious in the interaction between
> controller and model...if you use real models, then changes to the
> model code could very well lead to failing controller specs, even
> though the controller's logic is still correct.

In Java you don't mock domain objects because you want to program
toward an interface rather then a single concrete implementation.
Conceptually this still applies in Ruby, but because of differences
between the languages it isn't a 1 to 1 mapping in practice.

With regard to controllers and models in Rails, I don't want my
controller spec to use real model objects. The requirement of a model
existing can be found as part of the discovery process for what a
controller needs to do its job. If the implementation of a model is
wrong it isn't the job of the controller spec to report the failure.
It's the job of the model spec or an integration test (if its an
integration related issue) to report the failure.

When you make it a job of the controller spec to ensure that the real
model objects work correctly within a controller it is usually because
there is a lack of integration tests and controller specs are being
used to fill in the void.

Also, controllers can achieve a better level of programming toward the
"interface" rather then a concrete class by using dependency
injection. For example consider using lighight DI using the injection
plugin (http://atomicobjectrb.rubyforge.org/injection/):

  class PhotosController < ApplicationController
    inject :project_repository

    def index
      @projects = @project_repository.find_projects
    end
  end

There is a config/objects.yml file which exists to define what
project_repository is:
   ---
   project_repository:
     use_class_directly: true
     class: Project

This removes any unneeded coupling between the controller and the
model. Although the most common thing I've seen in Rails is to partial
mock the Project class in your spec. Although this works there is
unnecessary coupling between your controller a concrete model class.

>
>
> What is your opinion on isolating tests?

Tests should be responsible for ensuring an object works as expected.
So it's usually a good thing to isolate objects under test to ensure
that they are working as expected. If you don't isolate then you end
up with a lot of little integration tests. Now when one implementation
is wrong you get 100 test failures rather then 1 or 2, which can be a
headache when you're trying to find out why something failed.


> Do you try to test each
> class in complete isolation, mocking its collaborators?

Yes. The pattern I find I follow in testing is that objects whose job
it is to coordinate or manage other objects (like controllers,
presenters, managers, etc) are always tested in isolation.
Interaction-based testing is the key here. These objects can be
considered branch objects. They connect to other branches or to leaf
node objects.

Leaf node objects are the end of the line and they do the actual work.
Here is where I use state based testing. I consider ActiveRecord
models leaf nodes.

A practice that I've been following that was inspired from a coworker
has been that an object should be a branch or a leaf, but not both.
Most Rails applications don't follow anything like this and it's
common to find bloated controllers and bloated models (most people IMO
do not understand the Skinny Controller, Fat Model post, bloated
models are now becoming an up and coming trend unfortunately).

Objects and methods built-in the language, standard library or
framework are exempt from my above statements. If a manager
coordinates return values from methods being called other objects and
pushes them onto an array, I don't mock a call to Array.new.

> When you use
> interaction-based tests, do you always leave those in place, or do you
> substitute real objects as you implement them (and if so, do your
> changes move to a more state-based style)?

Leave the mocked out collaborators in place. An interaction based test
verifies that the correct interaction takes place. As soon as you
remove the mock and substitute it with a real object your test has
become compromised. It's no longer verifying the correct interaction
occurs, it now only makes sure your test doesn't die with a real
object.

If you do substitute in a real object, the only way you would be able
to maintain the integrity of the test is to partial mock your real
object to expect the right methods to be called. This will ensure that
the interaction continues to take place. But what happens is that the
test gets muddied up with things that don't need to be there.


> How do you approach
> specifying different layers within an app?

One way to think about this is is terms of composition and
inheritance. When layers interact using composition you treat it and
test it differently then if you use inheritance. For example a
ProjectsController using a @project_repository (see injection example
above) or a Project model subclassing ActiveRecord::Base.

I need to think about them some more though.

>  Do you use real
> implementations if there are lightweight ones available, or do you
> mock everything out?

For me it depends. With most Rails projects I've worked on there has
been one suite of integration tests against the application as a whole
and then a bunch of unit tests. The times this has differed are when
the application relied on third-party services. These services would
be replaced with dummy or lightweight implementations for my
integration tests (for example geocoding). Although there would be
another set of integration tests to specifically test our app against
the actual service.

An integration test should test that real objects are working together
correctly produce the intended system behavior. You should never mock
objects out at this level, but you may need to provide stub
implementations for third party services.

>
> I realize that's a ton of questions...I'd be very grateful (and
> impressed!) if you took the time to answer any of them.  Also I'd love
> to hear input from other people as well.
>

It's too bad we can't just stand at a whiteboard and talk this out.
The answers to these questions could fill a book and email hardly does
it justice to provide clear, coherent and complete answers. Not that
my response are "answers" to your questions, but it's how I think
about testing and TDD.

-- 
Zach Dennis
http://www.continuousthinking.com
http://www.atomicobject.com


More information about the rspec-users mailing list