Testing Legacy Code – Initializer blocks

Writing tests for legacy code may seem risky, even daunting: will we break our code? Will we need to rewrite extensive parts of our code in order to test it? Not many know that by following fairly simple practices we can start unit-testing our codebase with minimal risks. Here are two examples of how to overcome a well known obstacle – the “initializer blocks”.

Initializer blocks

Consider the following example:

public class Game {     private SystemSettingsDAO systemSettingsDAO = new SystemSettingsDAO();     private final GameStateDAO gameStateDAO;          public Game() {      } }

 As you can see in the code sample above, class Game has a private member SystemSettingsDAO, which is initialized in the declaration line. This line will be invoked the moment we will try to create an instance of the class Game:

Game game = new Game();

This type of initialization is called “initializer block”

The problem

When we say “Unit Tests” we mean “Good Unit Tests”. Good unit tests should not interact with any database, any network connection, any file system, or in short – they should not interact with any external resource.

Let’s say that in our code, SystemSettingsDAO is trying to open a connection to a database in its constructor (not a very uncommon scenario).

Any unit test that we will write for class Game will eventually access the database. Why? Because as we said earlier, when creating an instance of class Game, the constructor of SystemSettingsDAO is also being invoked.

Solution No. 1

One way to deal with this problem is Extract & Override. With this method, we extract the initialization line into a new method like this:

private SystemSettingsDAO systemSettingsDAO = getSystemSettingsDAO();     SystemSettingsDAO getSystemSettingsDAO() {     return new SystemSettingsDAO(); }

Pay attention to the access modifier (private/protected/public/etc.) of the new method – we need to be able to override it. Thus, in java it can be package default (empty) and in c# it should be at least protected.

In the test class, we create an inner class called GameForTest which inherits from Game and we override the getSystemSettingsDAO method like this:

public class GameTest {     @Test     public void test() throws Exception {         GameForTest gameForTest = new GameForTest();     }       private class GameForTest extends Game {          @Override         SystemSettingsDAO getSystemSettingsDAO() {             // return what ever you like... maybe a mock object         }     }  }

Now, when we create a new instance of GameForTest, the Initializer blocks in class Game will also be invoked as well as this line:

private SystemSettingsDAO systemSettingsDAO = getSystemSettingsDAO();

Only this time, the overridden method getSystemSettingsDAO of GameForTest will be invoked and not the original method in class Game. This ensures us that no database access will occur.

We can now write tests for class gameForTest which is the same as class Game except for this little override.

Solution No. 2

Did you know that the java compiler copies initializer blocks into every constructor of the class? For example:

public class Game {     private SystemSettingsDAO systemSettingsDAO = new SystemSettingsDAO(); }

Will turn into this code behind scenes:

public class Game {     private SystemSettingsDAO systemSettingsDAO        public Game () {         systemSettingsDAO = new SystemSettingsDAO();     } }

So, instead of letting the java compiler do this magic for us behind scenes, we can write the code like this manually. How does this help us you may ask. Well, if we write it manually this way, we can do that:

public class Game {     private SystemSettingsDAO systemSettingsDAO        public Game () {         this(new SystemSettingsDAO());     }       public Game (SystemSettingsDAO systemSettingsDAO) {         this.systemSettingsDAO = systemSettingsDAO;     } }

Now we can inject mocks of SystemSettingsDAO into class Game by using its parameterized constructor in our tests. More important is that old classes that used to create objects of class Game with its default constructor, are indifferent to this change.

public class GameTest {     @Test     public void test() throws Exception {         SystemSettingsDAO someMock = Mockito.mock(SystemSettingsDAO.class);         Game game = new Game(someMock);     }     }

Two important rules

Two important rules should be kept when we are trying to make legacy code testable:

  1. Make changes with minimal risks. Remember that you don’t have unit tests in place yet to protect you.
  2. Make changes with minimal effects. In our example, other classes that use class Game will not be affected by our changes.

You can easily see that in the suggested solutions above, these 2 rules were kept quite well.

If you, like many, are afraid of starting refactoring your legacy code
you want to take a look at this workshop.

Share on print
Share on facebook
Share on twitter
Share on linkedin
Share on whatsapp
Share on email