axelhodler

Make Java Legacy Code Testable via Seams

With the help of a technique called creating a seam, or subclass and override we can make almost every piece of code testable.

The first time I’ve seen this practice was in an awesome screencast of Sandro Mancuso called Testing and Refactoring Legacy Code. All the code snippets in this post are also taken from his screencast and shortened.

The technique is used to help cover existing functionality with tests before we start refactoring it. Remember:

Let’s say we have a static call inside a method which we want to unit test.

public List<Trip> getCurrentUsersTrips() {
  ...
  User loggedInUser = UserSession.getLoggedInUser();
  ...
}

We have no way to mock the UserSession and let the getLoggedInUser() method return a predefined user in our test. To do this we would have to change the way UserSession is used and thereby change the architecture of the program. As we try to test in small steps we will resist the urge to remove the static method calls and inject the UserSession into our class under test. Covering the UserSession.getLoggedInUser() will be achieved via a seam. Let’s extract the method call into a protected method:

protected User getLoggedInUser() {
  return UserSession.getLoggedInUser();
}

In our test file we will then create a testable implementation of our class under test:

private class TestableTripService extends TripService {
  @Override
  protected User getLoggedInUser() {
    return new User("Peter");
  }
}

Our getLoggedInUser() method was overridden. The rest of the implementation stays the same. Now we can return a User of our choice without the use of Mocks.

This will work as long as the class under test is not final. Since final classes can’t be subclassed.

But beware, these seams should not stay in your codebase for long. Ultimately they should be replaced with a design that does not need seams. In the example above this would happen by avoiding static methods and using dependency injection. Creating a seam should only be regarded as an intermediary step.

It’s a powerful technique. I’ve also seen it misused in a way to replace multiple lines of code at once, especially in methods that have multiple responsibilities and are often 100 lines and more in length. Please restrict their use to cases as the one above with the UserSession.