Dependency Injection, Unit Testing, and Information Hiding [closed]
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this questionSuppose you have a class Foo with private member of type Bar. You don't want users to know that Foo's implementation contai开发者_JAVA技巧ns a Bar and you don't want users to be able to create their own Bar and pass it through Foo's constructor, any other method, or a configuration file.
Edit: Bar is also problematic in that it accesses resources outside the control of your test environment such as a special database, network connection, or another process.
What do you do when you also want to be able to unit test Foo? Is dependency injection still possible? Does this mean Bar and Foo are too tightly-coupled (even though the dependency is one way) and that this situation is never an acceptable one to be in?
You never want to hide dependencies if you can help it.
If it's your code, you should make the dependency explicit by redesigning Bar
to accept the objects produced by the expensive resource rather than access the expensive resource directly: move the database access code (or whatever it is) out of Bar
and into Foo
, where you can inject test doubles.
If that's not practical (i.e., you're dealing with someone else's classes), Dror's list is very good.
[Disclaimer: I work at Typemock]
You have three alternatives:
- Create an internal method that sets the Bar class - using InternalVisibleTo in the assemblyInf.cs file you can enable your tests to set that class.
- Use DI to inject the Bar class, during test change that class with a dummy or fake.
- Use Faking/Mocking framework like Typemock Isolator (.NET) that enable you to set fake behavior on objects created within another class using
Isolate.Swap.NextInstance<Bar>.With(fakeBar);
If the Bar type is just an implementation detail of Foo, then your unit tests never need to worry about creating Bars since they should only exercise the public interface of Foo anyway.
EDIT: If Bar accesses external resources then I would make it an explicit dependency for Foo and then require an instance to be passed into Foo's constructor which could then be easily mocked for unit tests.
Your issue is not that you need to access a private member. That is only a symptom of the fact that the class in question is not designed to be isolated. It only allows one very specific usage, which is to new
up an object which goes to an expensive resource.
Even if you succeed in hacking around the untestable design, you will still only be doing integration tests. Unit tests are about isolation, which is the opposite of integration. Since that class cannot be isolated, by definition you cannot unit test it.
Is there a reason that externalizing the dependency is undesirable? What about this object exempts it from being dependency-injected like any other?
Edit:
I realize of course that you can isolate it with some trickery. However, those techniques bypass public interfaces, limiting future flexibility and increasing the likelihood that someone will break it (basically, nullifying the usefulness of private members).
精彩评论