Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Breaking Dependencies to Test Code in Java: Irritating Parameter

5.00/5 (1 vote)
18 Aug 2014CPOL4 min read 5.5K  
How to break dependencies to test code in Java: Irritating Parameter

Whenever I need to add a new feature to some legacy code, I attempt to add some tests around the code before I start making changes. This ensures that I don’t break the original purpose of the code. This is not always easy though as some classes and methods can be difficult to get into a test harness. One example I will talk about now is that of the Irritating Parameter which was pointed out to me in the book, Working Effectively with Legacy Code. If you haven’t read this book, I thoroughly recommend it as it explains ways of breaking dependencies to get classes under test, which I have found can be difficult when working with legacy code and large systems.

When a concrete class is passed as a variable to a method, or a constructor, it can make it very difficult to get the method/class under test. Take the following example:

Java
public class CreditValidator{
    public CreditValidator(JDBCConnection c, String u, String p) {
        Properties props = new Properties();
        props.put("user", u);
        props.put("password", p);
        c.setupConnection(DB_URL, props);
    }
    ...
}

If we want to add a method to this class, then we need to make sure we add tests so we don’t break any current functionality. To do this though, we will have to instantiate the CreditValidator in a test environment. The problem is the constructor takes in a JDBCConnection object that is used to connect to a database that is not available to our test environment. As you may have guessed, that object is the Irritating Parameter.

Mocking Frameworks

You may be thinking a mocking framework, such as EasyMock, would come in handy here as it would allow you to mock the JDBCConnection object and therefore bypass the setupConnection() method with ease. Well, you are correct, that would work, but it is not the best thing to do. If you are not in a massive hurry, due to the inevitable looming deadlines we all have, then it would be better to improve the design with some basic refactoring.

The problem with this class is that it is tightly coupled to the JDBCConnection object, which is a concrete class. This means the CreditValidator class must always connect to a database using a JDBCConnection. If, in the future, your boss wants to change the validation of credit so that it uses a different type of connection, then the CreditValidator is going to have to change (along with all the tests that should now be in place).

Extract Interface

A better solution would be to replace the JDBCConnection with an interface and then pass this into the constructor instead. This would mean the CreditValidator is coupled to an interface and not a specific implementation, which would make it loosely coupled. Your development IDE should have a way to do this for you easily, e.g. in Eclipse, you click Refactor in the menu bar and then Extract Interface and follow the wizard. So once we have done this, the code would look like this:

Java
public interface Connection {
    public void setupConnection(String url, Properties props);
}
Java
public class JDBCConnection implements Connection {
    @Override
    public void setupConnection(String url, Properties props) {
        // Set up JDBC specific connection
    }
}
Java
public class CreditValidator{
    public CreditValidator(Connection c, String u, String p) {
        Properties props = new Properties();
        props.put("user", u);
        props.put("password", p);
        c.setupConnection(DB_URL, props);
    }
    ...
}

Fake Object

Once you have created an interface, you will be able to create a fake object to pass into the CreditValidator constructor for your tests. To do this, create a FakeConnection class like this:

Java
public class FakeConnection implements Connection {
    @Override
    public void setupConnection(String url, Properties props) {
        return; // Do not setup a connection for our tests
    }
}

In your tests, you will easily be able to instantiate a CreditValidator now and know that setupConnection will not attempt to connect to a database. You have also cleaned up the design so that it is easier to add new features in the future.

There Is No Silver Bullet

Sometimes, you cannot extract an interface because the class of the parameter passed in is coming from a library. Take the DynamoHttpServletResponse object in ATG, for example – ATG is an ecommerce framework. This is passed into all ATG servlets and is a concrete class. We cannot create an interface to pass in so we have to come up with another solution to get our method into a test harness. Sometimes, we cannot improve the design but we still need to test the code. In this instance, we can use a mocking framework or we could just try passing null.

Pass Null

Java
public boolean invokeDefaultLogin(DynamoHttpServletRequest request,
        DynamoHttpServletResponse response, String email, 
        String password) throws ServletException, IOException {
    ProfileFormHandler pfh = getProfileFormHandler();
    boolean loggedIn = false;
    setCredentials(email, password);
    pfh.setLoginSuccessURL("/");
    boolean retVal = pfh.handleLogin(request, response);
    
    if (!pfh.getFormErrors()) {
        loggedIn = true;
    }

    return loggedIn;
}

In the code above, we can pass null as the request and the response as they aren’t being used in the method. However, they are being passed into the pfh.handleLogin() method and that means we should create a FakeProfileFormHandler and override handleLogin so that it returns true or false based on your test.

Extend and Override

Another technique that can be used to get the CreditValidator under test is called extend and override. With this technique, you create a new class that extends JDBCConnection and then you override the setupConnection method so that it just returns. In your test, you can now pass in your new class to the CreditValidator constructor and no database connection will be created.

Conclusion

The above examples are only trivial but you should take away from this that there are many ways to get classes/methods into a test harness when there is an irritating parameter. Ideally, you should clean up the design to make it more reusable and easier to maintain in the future but this is not always possible so using a mocking framework, passing null or using the extend and override technique will at least help you to break the dependency so that you can test your new feature.

License

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