[rspec-devel] stub_model

David Chelimsky dchelimsky at gmail.com
Wed Mar 19 09:16:46 EDT 2008

On Tue, Mar 18, 2008 at 10:45 PM, Zach Dennis <zach.dennis at gmail.com> wrote:
> On Tue, Mar 18, 2008 at 9:10 AM, David Chelimsky <dchelimsky at gmail.com> wrote:
>  > Hi all,
>  >
>  >  Over the last couple of years I've read a ton of mail from users
>  >  concerned with false positives coming from stubbing/mocking methods
>  >  that don't exist. I recently added a stub_model method for
>  >  rspec_on_rails (not yet released, available in git) which I think
>  >  mitigates this a bit. It is still in development and subject to
>  >  change, but here's how it works now.
>  >
>  >  It looks a lot like mock_model.
>  >
>  >   stub_model(Person, :name => 'David')
>  >
>  >  But it works in a fundamentally different way: FIrst, it creates a
>  >  real instance (which means you have to create the model to use it). It
>  >  assigns it an id by default, but you can set :id => nil if you want it
>  >  to behave like a new record. It overrides new_record? so that it
>  >  behaves as you would expect (false if there is an id, true if not). It
>  >  also overrides #connection, raising an error if there is any attempt
>  >  to access the database. This gives you the db isolation you get from
>  >  mock_model, but with a real object.
>  Does this mean you have access to real model methods? If I define
>  FooModel#bar and I use stub_model(Foo) in a controller and someone
>  updates the controller to call foo#bar will it complain that an
>  unexpected method was called or will it call the real foo#bar method?

The object IS a real model, so yes, it provides access to all of the
model methods.

>  I hope it blows up, otherwise it acts like partial mocking classes and
>  that has negative drawbacks.

It would fail if accessing that method caused some sort of error.

> Side rant: IMO partial mocking is evil
>  and should be avoided when they can be.

In general I agree, which is why it has taken me this long to arrive
at this solution. The motivation for me, personally, is that with a
mock object my view specs end up having to explicitly provide a lot of
stub values that the examples are not interested. They are just noise
that is present to keep the view moving.

It also strikes me that view specs are inherently state-based.

Take this for example:

describe "/people/show.html.erb" do
  it "should show the person's full name" do
    assigns[:person] = stub_model(Person, :full_name => "David Chelimsky")
    response.should have_tag(".name", "David Chelimsky")

To me, this is a very clear and simple example. We could debate that
it should be an interaction test, using
should_receive(:full_name).and_return(".."), but regardless of style
and intent, anyone with experience with view specs can read this and
it is very clear what is being expressed.

So let's say we add the person's email to the view:

  it "should show the person's email" do
    assigns[:person] = stub_model(Person, :email => "a at b.com")
    response.should have_tag(".email", "a at b.com")

With a mock object, both of these examples would now fail and I'd be
forced to supply both attributes for both examples. Sure, I could do
that by using a factory method (or object), but now I have to do this:

  it "should show the person's email" do
    assigns[:person] = create_person
    response.should have_tag(".email", "a at b.com")

But then if I change the value of the email address in the factory,
this example fails and I can't just look right at it to understand the
failure, I have to go look at the factory. So maybe I change the
expectation to use the created person's email.

  it "should show the person's email" do
    person = create_person
    assigns[:person] = person
    response.should have_tag(".email", person.email)

Except now, if the person's email is nil, and the tag happens to have
nil, the example will pass when it should fail. So maybe my factory
method takes arguments:

  it "should show the person's email" do
    assigns[:person] = create_person(:email => "a at b.com")
    response.should have_tag(".email", "a at b.com")

Now things are explicit and I'm still using a mock. Perhaps this is a
good solution using a mock object, but as the views grow, and we all
know they do, this becomes more and more work to maintain.

Using a real model instance and mocking/stubbing only what I expect in
a given example has allowed me to keep things much simpler in the view
specs and so far they have caused me no pain. Perhaps the lack of pain
has to do with the fact that views are generally not "interacting"
with the model, per se. They are simply grabbing and displaying
values. They are asking, not telling. Based on that, a mock object
almost seems like a waste.

> They clutter up tests with
>  cases that shouldn't be there, but have to be there in order to ensure
>  certain calls aren't made (where a real mock would yell at you for
>  calling a method you didn't stub or expect). End side rant. =)

I agree with you here in most cases. I'm coming to believe that views,
specifically views that rely on getters as Rails views do, are an edge
case when it comes to mock objects.

Consider an example from a CMS I'm working on. There is a content item
named promo, which has a bunch of attributes and near-zero behaviour.
Take a look at the code using mock_model (which wraps a mock object)
vs the code using stub_model (which wraps a real model object):

I think you'll agree that, given that the example is only interested
in form fields and not values, the latter is far superior for a number
of reasons. It is more clear. There is less noise. It is less brittle.
I'm sure there are others.

>  >  Kind of like unit_record, but on
>  >  an instance by instance basis.
>  >
>  >  I've been using this for a few weeks now (just introduced it to the
>  >  rspec code base a week or so ago) and I'm finding it very useful. I'm
>  >  thinking of changing the generated rails specs to use stub_model
>  >  instead of mock_model, and I'd be curious to hear your thoughts about
>  >  this.
>  I'm very eager to try this out. I'm interesting to find out more as
>  well. I shall sync with your git repository! Thanks for your hard work
>  David!

Thanks for your support.


>  --
>  Zach Dennis
>  http://www.continuousthinking.com
> _______________________________________________
>  rspec-devel mailing list
>  rspec-devel at rubyforge.org
>  http://rubyforge.org/mailman/listinfo/rspec-devel

More information about the rspec-devel mailing list