[rspec-devel] stub_model

Charles Grindel cgrindel at yahoo.com
Wed Mar 19 10:17:26 EDT 2008


To deal with model attribute access in my mocks, I took a slightly different approach.  First, I should mention that I use the Fixture Replacement plugin which allows you to easily create real model instances with reasonable attribute values.  (BTW, this plugin has been a real time saver.)  I realized that all I needed to do was marry the attribute definitions in Fixture Replacement's example_data.rb with RSpec's mock_model method.  So, I modified mock_model to recognize two parameters, :default_attribs and :create_attribs.  If you call mock_model without these parameters, things work as they would normally.  If you specify the :default_attribs parameter, it will retrieve the attributes from Fixture Replacement and stub them on the mock object.  If you specify the :create_attribs parameter, it will add values for :created_at, :updated_at, :created_on, :updated_on and :lock_version, if the model supports it.  Below is a typical use of this mechanism.

describe "/users/show.html.erb" do

  before(:each) do
    @user = mock_model(User, :default_attribs => true, :create_attribs => true)
    @user.person.stub!(:full_name).and_return('John Smith')
    assigns[:user] = @user
  end

  ...

end


If you are interested in perusing the code, it can be viewed at the following location.

http://pastie.caboo.se/167767


Chuck

----- Original Message ----
From: David Chelimsky <dchelimsky at gmail.com>
To: zach.dennis at gmail.com; rspec-devel <rspec-devel at rubyforge.org>
Sent: Wednesday, March 19, 2008 9:16:46 AM
Subject: Re: [rspec-devel] stub_model

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")
    do_render
    response.should have_tag(".name", "David Chelimsky")
  end
end

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")
    do_render
    response.should have_tag(".email", "a at b.com")
  end

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
    do_render
    response.should have_tag(".email", "a at b.com")
  end

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
    do_render
    response.should have_tag(".email", person.email)
  end

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")
    do_render
    response.should have_tag(".email", "a at b.com")
  end

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):
http://pastie.caboo.se/167751.

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.

Cheers,
David

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



-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://rubyforge.org/pipermail/rspec-devel/attachments/20080319/456ac671/attachment-0001.html 


More information about the rspec-devel mailing list