views:

203

answers:

4

Suppose you have a class Foo with private member of type Bar. You don't want users to know that Foo's implementation contains 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?

+1  A: 

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.

Lee
Good point, my question is too vague (editing)
insipid
What happens if Bar access the internet or a database - you still need to fake/mock/stub it
Dror Helper
+1: If Bar is completely hidden, it's not really testable, is it? If it has external dependencies, it should not be hidden. Any attempt to hide Bar is a bad design.
S.Lott
+4  A: 

[Disclaimer: I work at Typemock]

You have three alternatives:

  1. 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.
  2. Use DI to inject the Bar class, during test change that class with a dummy or fake.
  3. 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);
Dror Helper
+1 for the internal method idea.. unfortunately not all languages support internal methods such as java. I can't think of a way to accomplish the Dependency Injection.. perhaps through the adapter design pattern? Could you provide an example?
insipid
Turns out there is internal access at the package level in java, so I am wrong about that.
insipid
Alternately you could make the constructor internal (again, use InternalsVisibleToAttribute in .NET) and provide a public factory. Unless I was designing a public-facing API with security concerns, I wouldn't bother. @insipid - what language are you using?
TrueWill
Currently java, but this issue could happen to me in any language :)
insipid
Accepting answer, but if anyone decides to provide some Dependency Injection examples (no constructors or config files) I would appreciate it.
insipid
+4  A: 

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.

Jeff Sternal
+1: The design (completely hidden Bar) is a bad idea.
S.Lott
+1  A: 

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).

Bryan Watts
+1: Hiding Bar is a bad idea.
S.Lott