Reputation: 25312
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?
Upvotes: 3
Views: 610
Reputation: 1783
In the book Dependency Injection in .Net, Mark Seemann calls this last solution Bastard Injection, and considers it an Anti-Pattern. This is mainly because you still have the dependency on the concrete implementation. You should check out this book for more details.
In the case you have specified here it seems quite likely to me that you would have some other code, elsewhere, that produces whatever it is that is being validated. I'll call this the Creator. In turn it's likely that this Creator would also need an IChecksumGenerator. In which case I would let the DI container have full control over the dependency.
Imagine you wanted to switch in a different implementation for IChecksumGenerator. Assuming what I said above is true, you would need to change this in 2 places with the Bastard Injection; the Creator and the Validator. Letting the DI Container control it would mean that it is only in 1 place - the container configuration.
Another benefit to letting the DI container have control is that it reduces the chances that future changes will more closely couple MyValidator to the concrete IChecksumValidator by introducing LSP violations.
Upvotes: 2
Reputation: 233367
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.
Upvotes: 5
Reputation: 11477
I fail to see how MyValidator could be algorithmically coupled to IChecksumGenerator. IChecksumGenerator will be providing an contract to MyValidator that given a certain set of inputs a certain set of outputs will be returned.
How the implementation of IChecksumGenerator calculates these outputs is of no concern to MyValidator. This is why you are able to provide a test stub. The test stub has a hardcoded mapping between inputs and outputs to enable you to test. The real implementation will use an algorithm.
An algorithm can have many different implementations. There can be an implementation that optimises for memory usage and another one for speed.
As long as the correct outputs are provided for each possible input, MyValidator should not care about the implementation. Ensuring this occurs is what testing is all about.
Hpowever, if it really is algorithmically coupled and there is no way to separate the two, then they should probably not be separate classes.
Upvotes: 1
Reputation: 129744
This is not a violation of encapsulation. Validation often involves checksum.
I wouldn't be worried about a misconfigured ioc container as your fake or mock implementations don't exist in what you ship to production. Smoke tests will catch that immediately.
Hope this helps.
Upvotes: 3
Reputation: 7361
you need to separate your test code from product one.
in product code you can use:
var validator = new MyValidator(new MyChecksumGenerator());
in test code:
var validator = new MyValidator(new MyChecksumGeneratorStub());
where MyChecksumGeneratorStub implements IChecksumGenerator.
Upvotes: 0