This question has bothered me for a few days, and it feels like several practices contradict each other.
Example
Iteration 1
public class FooDao : IFooDao
{
private IFooConnection fooConnection;
private IBarConnection barConnection;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooConnection = fooConnection;
this.barConnection = barConnection;
}
public Foo GetFoo(int id)
{
Foo foo = fooConection.get(id);
Bar bar = barConnection.get(foo);
foo.bar = bar;
return foo;
}
}
Now, when testing this, I would fake IFooConnection and IBarConnection, and use Dependency Injection (DI) when instantiating FooDao.
I can change the implementation, without changing the functionality.
Iteration 2
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooBuilder = new FooBuilder(fooConnection, barConnection);
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Now, I won't write this builder, but imagine it does the same thing FooDao did before. This is just a refactoring, so obviously, this doesn't change the functionality, and so, my test still passes.
IFooBuilder is Internal, since it only exists to do work for the library, i.e it isn't a part of the API.
The only problem is, I no longer comply to Dependency Inversion. If I rewrote this to fix that problem, it might look like this.
Iteration 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
This should do it. I have changed the constructor, so my test needs to change to support this (or the other way around), this isn't my issue though.
For this to work with DI, FooBuilder and IFooBuilder need to be public. This means that I should write a test for FooBuilder, since it suddenly became part of my library's API. My problem is, clients of the library should only be using it through my intended API design, IFooDao, and my tests acts as clients. If I don't follow Dependency Inversion my tests and API are more clean.
In other words, all I care about as the client, or as the test, is to get the correct Foo, not how it is built.
Solutions
Should I simply not care, write the tests for FooBuilder even though it is only public to please DI? - Supports iteration 3
Should I realise that expanding the API is a downside of Dependency Inversion, and be very clear about why I chose not to comply to it here? - Supports iteration 2
Do I put too much emphasis on having a clean API? - Supports iteration 3
EDIT: I want to make it clear, that my problem is not "How to test internals?", rather it is something like "Can I keep it internal, and still comply to DIP, and should I?".
IFooBuilder
needs to be public?FooBuilder
need only be exposed to the DI system via some sort of "get default implementation" method that returns anIFooBuilder
and so can remain internal.public
for library APIs andpublic
for testing". Or in the other form "I want to keep methods private to avoid appearing them in the API, how do I test those private methods?". The answers are always "live with the broader public API", "live with tests through the public API", or "make methodsinternal
and make them visible to the test code".