Juanma Cervera
2008-Sep-13 10:24 UTC
[rspec-users] Mocking the dynamic composition of named_scopes.
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. Juanma Cervera -- Posted via http://www.ruby-forum.com/.
Pat Maddox
2008-Sep-13 16:35 UTC
[rspec-users] Mocking the dynamic composition of named_scopes.
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