[rspec-users] Mocking the dynamic composition of named_scopes.

Pat Maddox pergesu at gmail.com
Sat Sep 13 12:35:36 EDT 2008


Juanma Cervera <lists at ruby-forum.com> writes:

> Hello
>
> I am still learning to specify named_scopes, having some troubles and
> needing some help.
>
> This is the situation.
> I have already spec two or three named_scopes independently.
> but now, I want to spec a function that combines somes of these
> named_scopes dynamically.
> Something like this
>
>
>  class Thing
>
>   named_scope foo, .....
>   named_scope bar, ....
>   named_scope baz, ....
>
>   def complex_query(condition1, condition2, condition3)
>     scope = Thing.scoped({})
>     scope = scope.scoped( Thing.foo.proxy_options ) if condition1
>     scope = scope.scoped( Thing.bar.proxy_options ) if condition2
>     scope = scope.scoped( Thing.baz.proxy_options ) if condition3
>     scope
>   end
>  end
>
>  I don't know if there is a better way to implement it, but it works.
>  The problem is with the specification. I don't know how to do it with
> mocking.
>  I have already spec and test the named_scopes against the database and
> think
>  that this time I have to test only the chaining and not the result.

Hey Juanma,

I think it might be time to write a custom expectation matcher for
this.  There's no built-in support for specifing chained method calls
like you need (besides chaining mocks) - because chained method calls
are usually trainwrecks and trainwrecks are bad!

But named_scopes are kinda unique in that it doesn't matter what order
they're called in.  That is, chained named_scopes are compositional and
don't couple the client code to some internal structure.

Maybe you want a matcher like
Thing.should receive_scoped(:foo, :bar, :baz)

so you can do
Thing.foo.bar.baz
Thing.foo.baz.bar
Thing.bar.foo.baz
etc

or perhaps
Thing.should receive_scoped.foo("foo scope param").bar.baz(123)

I'm not 100% sure.  But, to tell you the truth, I would still avoid
that.  I'd just spec Thing.complex_query straight up, no mocks, and
hitting the db.  There are a few reasons:

* Using mocks here would mean you're mocking methods on the target
  object... which is sometimes okay but *usually* wrong.  At the very
  least, it's a smell indicating that you need just the slightest bit of
  force to compel you to extract it to a new object
* This would really be testing implementation details.  The fact that
  the complex_query uses named_scopes is incidental, really - you could
  imagine writing the query with find_by_sql, doing it all in memory,
  whatever.  Best just to specify that it returns what you want.  There
  aren't any interesting collaborators here you want to isolate your
  code from
* Economics - you can take the hit of slightly duplicated specs (if you
  add constraints to Thing.foo then you'll have to update its spec as
  well as Thing.complex_query).  Or you could spend your time writing a
  non-trivial custom matcher that encourages bad habits.  Personally, I
  would go for the straight-forward specification that gives great
  examples of expected behavior, even if it means there's a tiny bit of
  duplication.

One thing's clear though - you're on the right track!  Encapsulating the
scope composition in Thing.complex_query is a great idea.  See
http://evang.eli.st/blog/2008/7/23/when-duplication-in-tests-informs-design
for some more of my detailed thoughts on the technique you're using.

Cheers,
Pat


More information about the rspec-users mailing list