A small note about collaborators

It’s often the case that a class has one or more collaborators that are used by many of its methods. When you want to override one for testing, you can either pass the test double into the constructor (probably as a default argument) or you can smash the double into an instance variable you happen to know points to the collaborator.

I used to use the constructor approach, but I increasingly find myself using the Hulk! Smash! approach. It looks like this…

The object-to-be-tested names its collaborators and gives them the values they’ll use in production:

I do this because I think of the collaborators as a static, declaration-like property of the class. (If I were ambitious, I’d convert collaborators_start_as into a class-level declaration like this:

I haven’t been that ambitious yet, as it turns out.)

Each test declares which collaborators it overrides:

Note that (in Ruby), I can even mock out classes, so I don’t have to go through contortions to pass in instances that an outside caller really would have no particular interest in:

With several of the mocking packages available to me, I wouldn’t even have to go through the indirection of putting the class ThingSource into an instance variable. I could do this:

I’m not bold enough to do that yet.

3 Responses to “A small note about collaborators”

  1. xpmatteo Says:

    You are assuming that every class always uses a fixed set of collaborators? Would it not be nice if a class could be used at runtime in different contexts, with different collaborators, or with collaborators that are configured in different ways? This would require to have the collaborators declared some place else than in the class itself.

  2. MarkKnell Says:

    If I’m understanding, you have to keep your collaborators hash up to date with anything you want to smash in for tests (not hard) and make sure the code always collaborates via that mechanism (not hard but harder).

    This seems like a manageable amount of bookkeeping, but more than I like. The collaborators has seems like a formalized point of indirection, to support tests but not something you’d otherwise do for the production code (in Ruby, which doesn’t have interfaces). Is this a fair characterization? Would you do this if not for tests?

    In dependency inversion frameworks in C#, a class retrieves its collaborators from a service, either explicitly (by having a reference to the service) or implicitly (by decorating methods and constructors for injection), no bookkeeping required. Or rather, your class and its collaborators already have the bookkeeping of being represented in both classes and interfaces, and for the DI framework you bind the class to the interface somewhere; all that bookkeeping is required elsewhere, so there’s no *additional* bookkeeping inside the class. To use test doubles, you just change the DI bindings, from production objects to the doubles.

    Interesting–I think I’d rather have to do two extra things (interfaces + DI binding) if it’s unchanged between production and testing, than do one extra thing (the collaborators hash) for testing only. I may be less lazy than I thought.

  3. Ben Butler-Cole Says:

    I haven’t been able to understand the motivation for this approach. Generally in my unit tests I *never* want to use the collaborators that will be used in production. And often in my production code there is exactly one place where I instantiate a given class.

    So defaulting to the production collaborators seems like optimizing the wrong thing. I read and write the instantiation calls in tests far more often then I read and write them in production code, so I want to make that as easy as possible; I really don’t mind having to pass the collaborators to the constructor in production code.

    Can you explain your motivation for doing this further?

    Thanks
    Ben

Leave a Reply

You must be logged in to post a comment.