First let me introduce implementation without Dependency Injection (which will break Dependency Inversion Principle):
public class MyValidator
{
private readonly IChecksumGenerator _checksumGenerator;
public MyValidator()
{
_checksumGenerator = new MyChecksumGenerator();
}
...
}
To make this code testable let inject IChecksumGenerator:
public class MyValidator
{
private readonly IChecksumGenerator _checksumGenerator;
public MyValidator(IChecksumGenerator checksumGenerator)
{
_checksumGenerator = checksumGenerator;
}
...
}
Now we can easily test MyValidator and stub checksumGenerator if required. But MyValidator implementation is algorithmically coupled to specific IChecksumGenerator implementation (it just won't work with any other implementation). So some problems appear:
The best solution I came to is the following:
public class MyValidator
{
private readonly IChecksumGenerator _checksumGenerator;
public MyValidator()
{
_checksumGenerator = new MyChecksumGenerator;
}
internal MyValidator(IChecksumValidator checksumValidator)
{
_checksumValidator = checksumValidator;
}
...
}
Here I introduce special constructor for testing purposes (so I can stub IChecksumValidator in tests) but public constructor creates that implementation which it is coupled to (so encapsulation is not broken). It is a bit ugly to create some code for testing purposes but looks like it makes sense in this case.
How would you solve this problem?
Refactoring to Constructor Injection is a very good idea, but I find the constraints put forward in the question strange. I'd recommend that you reconsider the design.
If MyValidator only works with one specific implementation of IChecksumGenerator it would be violating the Liskov Substitution Principle (LSP). Essentially, that also means that you wouldn't be able to inject a Test Double since the stub/mock/fake/whatever wouldn't be an instance of the 'correct' IChecksumGenerator.
In a sense you could say that the API lies about its requirements because it claims that it can deal with any IChecksumGenerator, while in reality it only works with one specific type - let's call it OneAndOnlyChecksumGenerator. While I would recommend redesigning the application to adhere to the LSP, you could also change the constructor signature to be honest about the requirement:
public class MyValidator
{
private readonly OneAndOnlyChecksumGenerator checksumGenerator;
public MyValidator(OneAndOnlyChecksumGenerator checksumGenerator)
{
this.checksumGenerator = checksumGenerator;
}
// ...
}
You might still be able to turn the OneAndOnlyChecksumGenerator into a Test Double by making strategic members virtual so that you can create a test-specific child class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With