8

I have a large codebase with a lot of "anti-pattern" singletons, utility classes with static methods and classes creating their own dependencies using new keyword. It makes a code very difficult to test.

I want to gradually migrate code to dependency injection container (in my case it's Guice, because it is a GWT project). From my understanding of dependency injection, it's all or nothing. Either all classes are managed by Spring/Guice or none. Since the codebase is large I cannot transform the code over night. So I need a way to do it gradually.

The problem is that when I start with a class that needs to be injected into other classes, I cannot use a simple @Inject in those classes, because those classes are not managed by container yet. So this creates a long chain up to the "top" classes that are not injected anywhere.

The only way I see is to make an Injector / application context globally available through a singleton for the time being, so that other classes can get a managed beans from it. But it contradicts important idea of not revealing the composition root to the application.

Another approach would be bottom-up: to start with "high-level" classes, include them into dependency injection container and slowly move down to "smaller" classes. But then I have to wait a long time, since I can test those smaller classes that still depends on globals/statics.

What would be the way to achieve such gradual migration?

P.S. The question Gradual approaches to dependency injection is similar in title, but it doesn't answer my question.

  • 1
    Do you want to go straight from having hard coupled classes to using an dependency injection container? You first have to make some refactoring to decouple classes, using interfaces before even thinking on marrying to any dependency injection framework or tool. – Tulains Córdova Feb 17 '16 at 12:18
  • Fair enough. So I have some utility class A that is used in 30 other classes (one of them is class B), making them untestable. I thought to make class A a constructor dependency of class B. I have to change all calls to constructors and pass A inside. But where should I get it from? – damluar Feb 17 '16 at 12:28
  • If B is the smallest unit you can apply DI to in order to get started, then I see no harm in constructing A wherever you're constructing B for the time being - provided A has no dependencies. Once the ball is rolling, you can come back and refactor it. – Andy Hunt Feb 17 '16 at 12:34
  • @damluar A factory to begin with? – Tulains Córdova Feb 17 '16 at 13:00
  • 1
    @damluar a class does not become untestable just because it relies on a utility class. It just means your unit is slightly bigger than you want it to be. If you can test your utility class by itself, then you can use it in tests for all other 30 classes without worry. Read Martin Fowlers blog about unit testing. – gbjbaanb Apr 4 '16 at 15:18
2

Sorry C# is my language of choice, I can read Java but would probably butcher the syntax trying to write it... The same concepts apply between C# and Java though, so hopefully this will show the steps in how you could gradually move your code base to be more testable.

Given:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

could easily be refactored to use DI, without the use of an IOC container - and you could potentially even break it down into several steps:

(potential) Step one - take in dependencies but no change to calling (UI) code:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

Refactor 2 (or first refactor if implementing IOC container and changing calling code immediately):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

Step 2 could technically be done by itself - but would (potentially) be a lot more work - dependent on how many classes are currently "newing up" the functionality you're looking to DI.

Consider going with the step 1 -> step 2 route - you'd be able to create unit tests for Foo, independent of Bar. Whereas prior to the step 1 refactor it was not easily accomplished without using the actual implementation of both classes. Doing step 1 -> step 2 (rather than step 2 immediately) would allow smaller changes over time, and you'll already have your start of a test harness in order to better ensure your refactor worked without consequence.

0

The concept is the same whether you are using Java, PHP, or even C#. This question is dealt with fairly well by Gemma Anible in this YouTube video:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, sorry!)

You replace the untestable code with "facade" (for lack of a better term) that calls new, testable code. Then gradually you can replace the older calls with the injected services. I've done this in the past and it works fairly well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.