There's a good back and forth going on about something I've been thinking about for quite a while. Jacob Proffitt is basically claiming that DI's primary benefit is mockability in unit tests and that we should all but ditch it in favor of using TypeMock to mock our tests. Meanwhile, Ayende and Nate Kohari have been defending DI with reasons like "getting low coupling between our objects" (Ayende) and "simply put, dependency injection makes your code easier to change" (Nate).
Well, I think I'm just going to have to agree with both of them... to an extent. I agree that DI promotes loose coupling, but I disagree that TypeMock is too powerful (even though I said just that in our podcast with Hanselman, I'm allowed to change my mind). I think that DI has its place, and is not a Silver Bullet. There have been a number of times when I was refactoring a class, pulling out a method object, or moving something to a a sub service (in my attempts to adhere to the Single Responsibility Principle) that I've questioned whether or not that new class warrants all of the following:
- Having an interface.
- Being added to the container and being injected.
- Writing new tests for just that, even though tests were already passing and will continue to pass for its parent object with the same coverage %.
- Being mocked out of the original tests.
The problem with doing #4 with a framework like Rhino Mocks is that it requires you to do #3 (obviously) as well as #1 & #2 . You can't mock unless your methods are virtual or you're interfaced and that mock is injected. With TypeMock I can do #3 and #4 without worrying about adding my new class to the container. So why not add it to the container? Well, because a great deal of the time You Aren't Gonna Need It. I think once one other class takes a dependency on that service you have enough justification to control its creation in one place by adding it to the container. However, a great deal of the time, these one off objects you create to increase readability and maintainability in your code just end up either changing the way you test your objects in that you're testing more than one class at a time, or you end up with an interface and constructor argument explosion solely so that you can mock them in tests. In situations like that, Jacob is right--the only real benefit is mockability. Maybe we should consider using something like TypeMock in these situations? Just because something is powerful doesn't mean its evil. We just need to exercise more caution and use it only in times its warranted.
That said, I've never actually used TypeMock and this is all purely conjecture based on what I've read about it. I just think that we can learn something from the Ruby guys and from people with the viewpoints of Eli and Jacob, as long as everyone realizes that neither sides of the camp is producing Silver Bullets.