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.
- Does the client ask the right questions of the interface? Checked through mocking and verification.
- 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?
File
is not a part of the interface and questions asked to the interface cannot involveFile
at all. The only think clients can ask the interface isloadRacords()
. In order to ask something else they must talk to somebody else.FlatFileLoader
would be a contract test. But my point is that the particularFile
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 fakeFile
.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)