Unit-testing – Gradual approaches to dependency injection

dependency-injectionunit testing

I'm working on making my classes unit-testable, using dependency injection. But some of these classes have a lot of clients, and I'm not ready to refactor all of them to start passing in the dependencies yet. So I'm trying to do it gradually; keeping the default dependencies for now, but allowing them to be overridden for testing.

One approach I'm conisdering is just moving all the "new" calls into their own methods, e.g.:

public MyObject createMyObject(args) {
  return new MyObject(args);
}

Then in my unit tests, I can just subclass this class, and override the create functions, so they create fake objects instead.

Is this a good approach? Are there any disadvantages?

More generally, is it okay to have hard-coded dependencies, as long as you can replace them for testing? I know the preferred approach is to explicitly require them in the constructor, and I'd like to get there eventually. But I'm wondering if this is a good first step.

One disadvantage that just occurred to me: if you have real subclasses that you need to test, you can't reuse the test subclass you wrote for the parent class. You'll have to create a test subclass for each real subclass, and it will have to override same the create functions.

Best Answer

This is a good approach to get you started. Note that the most important is to cover your existing code with unit tests; once you have the tests, you can refactor more freely to improve your design further.

So the initial point is not to make the design elegant and shiny - just to make the code unit testable with the least risky changes. Without unit tests, you have to be extra conservative and cautious to avoid breaking your code. These initial changes may even make the code look more clumsy or ugly in some cases - but if they enable you to write the first unit tests, eventually you will be able to refactor towards the ideal design you have in mind.

The fundamental work to read on this subject is Working Effectively with Legacy Code. It describes the trick you show above and many, many more, using several languages.