On Nov 26, 2010, at 2:24 AM, medihack wrote:
David, sorry for double posting (it seems I am working too much and
forgetting about what I already asked) … and thanks for your answer.
How about a bit more convenient way for future releases. Something
like:
MyModel.stub_chain(:tag_counts, { :offset =>
0 }, :limit, :order).and_return([])
it could also work for multi parameters:
MyModel.stub_chain(:tag_counts, { :my_method => [:param1, :param2,
param3] }, :limit, :order).and_return([])
What do you think?
I would be opposed to this feature.
There is an old guideline in TDD that suggests that you should listen to
your tests because when they hurt there is usually a design problem.
Tests are clients of the code under test, and if the test hurts, then so
do all of the other clients in the codebase. Shortcuts like this quickly
become an excuse for poor designs. I want it to stay painful because it
should hurt to do this.
I understand that chains like this are common in Rails apps thanks to
good ideas like composable finders (which generally do not violate
Demeter), but I don’t think the parallel chains should appear in client
code or in specs. e.g. if this is a controller spec, the model should
expose a single method that wraps this, and if it’s the model spec, the
spec should just call the method that wraps the chain with different
inputs and and specify the expected outcomes.
Even if I were in favor of the concept, the example above is confusing
because it is a stub that becomes a message expectation. Constrained
stubs like this (e.g. double.stub(:method).with(:arg).and_return(value))
are confusing enough as it is because they don’t look like message
expectations, but they act sort of like them. I’ve considered
deprecating “with” on stubs and pushing toward an inline fake
implementation instead:
account.stub(:withdraw) do |*args|
if args.first == Money.new(200, :USD)
# do something
else
# do something else
end
end
RSpec already supports this, and it makes the intent of the spec much
more clear and localized than:
account.stub(:withdraw).with(Money.new(200,
:USD)).and_return(TransactionResult.new)
In this case, if account receives withdraw() with anything other than
Money.new(200, :USD), the outcome needs to be defined elsewhere in the
spec, and these things quickly become spread out and difficult to
understand.
That’s my 2, but feel free to try to convince me otherwise
Cheers,
David