Click here to Skip to main content
15,881,803 members
Articles / All Topics

Breaking Dependencies: When a Constructor Instantiates Objects

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
3 Sep 2014CPOL5 min read 6.2K   2   1
Breaking Dependencies: When a Constructor Instantiates Objects

I was speaking to a colleague recently during one of our fortnightly tech forums and he mentioned a specific class that he found difficult to get under test. The problem was that the constructor of the class was attempting to instantiate and configure new objects that were difficult to setup in a test harness. When you are faced with a constructor that instantiates classes internally, there are a few different ways to break these dependencies safely. This article describes some of them and the reasons for using each one.

Parameterise Constructor

When confronted with a constructor that instantiates one or two classes internally, then we can use a technique called parameterise constructor to get the class into a test harness. Take the example class below.

C#
public class ForeignExchange() {
    private String currency;
    private Converter converter;
    public ForeignExchange(String currency) {
        this.currency = currency;
        this.converter = new CurrencyConverter(currency);
    }
}

When the ForeignExchange class gets instantiated, it creates a CurrencyConverter object, which takes a parameter to determine which currency the converter is for. The problem we have is we don’t know if we can instantiate the ForeignExchange class as it is dependant on the CurrencyConverter class being instantiated. So what do we do in this situation? The first thing should always be to try and instantiate it in a test harness. If it fails, you’ll be told why pretty quickly.

So, we’ve tried to instantiate the object in a test harness and it has failed because the CurrencyConverter constructor calls an external system that we cannot replicate easily in a test harness. We aren’t really bothered about testing the CurrencyConverter though, as we are only trying to get the ForeignExchange class into a test harness so we can alter some of the behaviour. Therefore, we want to bypass the CurrencyConverter object so we can concentrate on our changes. How do we bypass an object that is instantiated in the constructor internally? With parameterise constructor.

This is really simple, all you do is pass in the object you wish to break the dependency on, e.g.:

C#
public ForeignExchange(String currency, <span style="background-color:#fffaa5;">Converter converter</span>) {
    this.currency = currency;
    this.converter = converter;
}

You then create another constructor to mimic the original one, i.e., passing the original parameters, so that any existing code doesn’t break, e.g.:

C#
public ForeignExchange(String currency) {
    this(currency, new CurrencyConverter(currency));
}

Now you can create a test by passing null as the Converter to the new constructor or you can mock/fake the class instead.

One downside to this refactoring is that it opens the code up slightly as there are now 2 public constructors that any developer can use in the future. Remember that this is a technique to break a dependency safely so that you can get your tests in place for the new code you are going to write. We don’t always have the appropriate time to refactor the code in the way we want to but we should ALWAYS get any new code we write under test. This method may not be pretty, and may come with its issues, but it will allow you to test your code, and therefore, I think it is a worthy trade-off in some situations.

Extract and Override Factory Method

When a constructor attempts to instantiate a few objects, the parameterise constructor technique is not the most ideal method for getting the class under test. The reason for this is because the parameter list for the constructor can become too large. Generally, if there are more than 3 parameters in a method signature, you should be thinking of ways to refactor the method. So, what do we do with the method below:

C#
public class  NavigationAccessor {
    private Navigation navigation;
    public NavigationAccessor() {
        Site site = SiteContextManager.getCurrentSite();
        List<NavigationFacet> facets = new ArrayList<NavigationFacet>();
        NavigationItems items = site.getNavItems();
        for (NavigationItem item : items) {
            NavigationFacet facet = new NavigationFacet(item);
            facets.add(facet);
        }
        TopNav topNav = new TopNav(items);
        LeftNav leftNav = new LeftNav(items);
        navigation = new Navigation(site, facets, topNav, leftNav);
    } 
}

This constructor uses a static class to get a site and then creates a list of NavigationFacets, a TopNav object, a LeftNav object and a Navigation object – that’s a lot of work to instantiate a NavigationAccessor. We can’t use parameterise constructor because we’d have to pass in 4 parameters. So instead, we can extract the method and then create a new class that extends and overrides the object creation.

In the example above, all of the code helps to instantiate a Navigation object. Therefore, we first extract it all out to a new method called createNavigation() using the ‘Extract Method’ function of your IDE.

C#
public class NavigationAccessor() {
    private Navigation navigation;
    public NavigationAccessor() {
        navigation = createNavigation();
    }
    protected Navigation createNavigation() {
        Site site = SiteContextManager.getCurrentSite();
        List<NavigationFacet> facets = new ArrayList<NavigationFacet>();
        NavigationItems items = site.getNavItems();
        for (NavigationItem item : items) {
            NavigationFacet facet = new NavigationFacet(item);
            facets.add(facet);
        }
        TopNav topNav = new TopNav(items);
        LeftNav leftNav = new LeftNav(items);
        return new Navigation(site, facets, topNav, leftNav);
    } 
}

I’ve made the createNavigation() method protected because we will need to override this for our tests. Again, this is bad design because this opens this method up to being extended or overridden. However, although we wouldn’t choose to do this normally, this is a tool to get legacy code under test safely so that we can add our new code without worrying that we are breaking the existing functionality. Therefore, it may be a necessary evil.

The next step is to create a new class that extends the NavigationAccessor then override the createNavigation() method to return a FakeNavigation object (or null, if not required).

C#
public class FakeNavigationAccessor extends NavigationAccessor {
    @Override
    protected Navigation createNavigation() {
        return null;
    }
}

Now we can instantiate a FakeNavigationAccessor class in our test harness and the constructor will set the ‘navigation’ object to null. This means we can concentrate on getting our tests in for our new code safely.

Conclusion

Neither of these methods generated particular great design. The reasons to use these techniques are to help you get some legacy code into a test harness in a safe way. Ideally, you would put tests around all of the code and then refactor it to make the design better but we don’t always have the time to do this. Instead, you can use these methods to safely alter the code, without writing unit tests, so that you can get the class instantiated and, therefore, can test your new code. The key point of these techniques is to get your new code tested. If you have some spare time after you have finished your code, then you can always go back and try to improve the design and remove some of the nasties that were introduced.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I’m a software developer for an online retail company and I have a passion for coding and learning. I am constantly trying to improve my skills and most importantly my designs. I’m hoping to help other developers write good, clean code and learn the parts I’m getting wrong so I can fix them. So if you think you can contribute to something then send me an email or comment on one of my blogs and I’ll try and respond as soon as possible.

Comments and Discussions

 
Generalsurprised Pin
Southmountain18-Jan-20 8:24
Southmountain18-Jan-20 8:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.