Suppose I have an interface declaring the following method signature:
SearchResults SearchProducts(string type, string filter, string anotherFilter);
Inside a concrete implementation of this, I instantiate a helper class (call it QueryBuilder
), built to ease the construction of strongly-typed search queries for the specific search library I'm using. I want to unit test SearchProducts
(ideally by mocking the query builder), which means I need to decouple the method from QueryBuilder
.
How do I do this appropriately when the two things are logically coupled? That is to say:
- It's not appropriate to pass an
IQueryBuilder
into the method as the implementation ofIQueryBuilder
is tightly coupled to the implementer ofISearchProducts
, i.e. the types of the return values ofQueryBuilder
's methods are specific to the library being used in the concreteSearchService
. - It's not appropriate to pass an instance of
IQueryBuilder
into theSearchService
constructor (via DI or directly) as the logical scope of the query builder instance is local to theSearchProducts
method call - it is instantiated in the method, its state is manipulated by adding sub-queries and a complete "query" is extracted for thisSearchProducts
call).
So, given the above, what's the appropriate way of handling this? It sounds like a case for a factory; however the implementation of this factory would be trivial and it seems a little contrived to define a factory interface and add a constructor parameter just for the sake of testability.
Is this indicative of a fundamental flaw in the design of my search interface? How do I decouple these things appropriately such that I can test both the search service and the query builder implementations in isolation?
QueryBuilder
is an implementation detail. You can produce equally valid implementations that don't use it. So I wouldn't mock it, even if it increases the size of the system under test.QueryBuilder
has methods with logic sufficiently complex that I would like to test it in isolation, which means that not mocking it undermines the usefulness of testingSearchService
, no?