Ok, well it seems that the answer to my direct question is that no,
there’s
no way to test message expectations without an explicit receiver; thanks
for
the responses.
I suppose I should respond to the comments re: more general issue of
testing
strategy [hopefully this doesn’t ignite too long of a thread]:
I completely agree that the goal should be to disconnect testing from
implementation as much as possible. In general, this requires finding
the
simples set of N inputs to a method that cover all the basic behavior
you
want to test for, and making sure that the appropriate output is
generated
for all of those cases.
This may work for the majority of methods you encounter; but it’s not
always
practical. Sometimes a method is complex enough that the N inputs you
would
need to describe all behavior can be too large to be practical. We want
to
test thoroughly and to test in a way that’s easy to maintain. Overall
it is
easier to maintain tests that don’t rely at all on implementation, but
it is
also easier to maintain a smaller number of tests than a very large
number
of tests. These two things have to be balanced.
So, I do agree with the general rule, but it doesn’t apply in every
situation. And, to be clear, this has nothing to do with testing
protected
methods, which of course shouldn’t be done. Here’s an example to
illustrate
my point:
Suppose methodA does the following:
- parse a data file uploaded by the user
- reformat the parsed data to deal with a variety of different
allowable
configurations
- perform calculations on the collected data
In the implementation, I might refactor the functionality into three
smaller
methods (one for each bullet), and just call those three smaller methods
from within methodA. Those other methods will also be public (they
might
need to be called from elsewhere), and I test each of them separately.
So,
having tested all of the functionality described by those three bullets
separately, all I really care about in testing methodA is that it is
actually calling those three methods. Otherwise, re-testing all that
functionality is not at all DRY.
Even if I wanted to re-test all of the functionality, it can be next to
impossible. Suppose I write 20 tests to fully spec out each of the
three
methods called from within methodA (because there are 20 distinct sets
of
behaviors that describe all the possible behaviors of each of those
methods). In this case, if I wanted to test methodA without referencing
any
internal logic at all, I might be required to write 20^3 = 8,000 tests
to
fully cover all of the logic described by the 60 tests used to cover the
three subroutines.