1

In J.B. Rainsberger's discussion of collaboration tests he describes how tests for "clients" (users of a particular interface) should have two parts to their test.

  1. Does the client ask the right questions of the interface? Checked through mocking and verification.
  2. Does the client react properly when it receives the answers? Checked through stubbing return values from the interface.

My question is in relation to part 1 ("does the client ask the right questions?"). He uses the example that we could check that the client sends the correct query to a generic database interface. This would be checked by verifying a method call on an interface like query(String qry). All the useful information is contained within a parameter of an interface method.

But this seems to conflict with another goal of using pluggable data layers. Imagine a generic record loader that loads a list of Strings.

public interface RecordLoader {
    public List<String> loadRecords();
}

In this case we want to have a concrete FlatFileLoader that loads records. We obviously can't put the File in the interface, because then it would no longer be possible to implement a hypothetical DatabaseLoader. No problem, the FlatFileLoader can take a File as its constructor argument.

public class FlatFileLoader implements RecordLoader {
    public FlatFileLoader(File sourceFile) {
        // ...
    }
}

But now we've created a gap in our test coverage -- the particular File that's being used to load the records is arguably a key part of the question that the client (say an EmployeeLoader) is asking the interface, and we can't verify that this is correct. We were able to do this in the database example, abetted by the fact that the query is an untyped String. Is the point that we should be creating even more specific interfaces that take well-typed parameters for all their state? Or should we be pushing the details of the concrete RecordLoader somewhere else -- and in that case, where does it get tested?

4
  • 5
    I’m not sure I understand your question. In a test, if you’re testing the concrete implementation of FlatFileLoader, you’d inject a mock File and assert that the correct methods on it are called.
    – Paul
    Oct 20, 2017 at 12:38
  • File is not a part of the interface and questions asked to the interface cannot involve File at all. The only think clients can ask the interface is loadRacords(). In order to ask something else they must talk to somebody else. Oct 22, 2017 at 0:46
  • @Paul Within the terminology mentioned earlier, "collaboration/contract", the test of the concrete implementation of FlatFileLoader would be a contract test. But my point is that the particular File itself that gets loaded by the class is now a constructor argument and is not itself tested. Even if you create a mock in the contract test you're going to be used a fake File.
    – amoe
    Oct 23, 2017 at 5:44
  • (Although in most programs this particular File would be a trivial matter and more like configuration than code -- and possibly specified in DI container config -- this applies to any constructor argument it seems). So does it follow that since any constructor argument can't be specified by the interface, any complex logic that results in the creation of constructor arguments needs to be factored out somewhere else in order to be testable? (e.g. Factory class)
    – amoe
    Oct 23, 2017 at 5:46

1 Answer 1

1

My question is in relation to part 1 ("does the client ask the right questions?"). He uses the example that we could check that the client sends the correct query to a generic database interface. This would be checked by verifying a method call on an interface like query(String qry). All the useful information is contained within a parameter of an interface method.

But this seems to conflict with another goal of using pluggable data layers. Imagine a generic record loader that loads a list of Strings.

Then this pluggable data layer is another dependency you mock and you check, that the string returned by the pluggable data layer mock (according to part 2) is passed to the generic database interface method (according to part 1).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.