Juanma Cervera <[EMAIL PROTECTED]> 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
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users