Having read "Growing Object-Oriented Software Guided by Tests" (GOOS), I
finally stopped worrying and learned to love the "mockist" or "London School"
of TDD. However, the unfortunate fact of this style in Ruby is that tests using
mocks can easily find themselves out of sync with the concrete implementations
instructed into existence by those mocks, especially when making adjustments to
the design of the system. The result is a bunch of green tests when the truth
is that the system may not actually integrate. Of course, this wasn't a problem
in GOOS as the authors used Java (blech), which is a statically-typed, compiled
language, meaning the build would fail if method signatures went out of sync.
One solution is to do integration testing of your subsystems. This is
suboptimal. They may not be as slow as end-to-end acceptance tests, but
integration tests are slow. And there are simply too many possible paths within
an integrated system to be completely confident that the tests are giving the
vital feedback you want. Still, this approach is better than nothing at all.
Another solution is to use something like rspec-fire
(https://github.com/xaviershay/rspec-fire). It allows you to specify the class
for which your mock is standing in. When unit testing in isolation, the option
is ignored, but when running your entire test suite, tests will fail if the
mocks include stubs or expectations which are not implemented by the specified
class. I think this is a big step in the right direction, but it doesn't quite
hit the mark. When using mocks to drive out the design of collaborators, you
should be mocking roles rather than concrete classes, roles which your system
may at some point want more than one class to implement.
Another approach I found is rspec-roles
(https://github.com/joakimk/rspec-roles). This one is halfway there. It's like
the missing half of rspec-fire. It allows you to define roles explicitly and
then assert in your class specs what roles the class plays, but it doesn't
have any impact on your mocks.
To that end, I thought about a combined approach which could hopefully resolve
the issue with minimal effort. It's included in the Gist below. This is purely
a hypothetical syntax, and I haven't started any attempt at actually
implementing it. I thought I'd throw it out for the community to talk about and
maybe get the ball rolling on something like it (or something better).