Alexander Beletsky's development blog

My profession is engineering

Refactoring to testability

Suppose you are working on some web REST API adapter. It would be basically one class with bunch of methods. Each method would represent each supported API call. Through the implementation you would probably landed with something like this,

public class ApiAdapter
{
    private HttpClient _client;
    private RequestFormatHelper _requestFormatHelper;
    private ResponseFormatHelper _responseFormatHelper;

    // ...
    
    public ApiAdapter()
    {
        _client = new HttpClient(/* ... */);
        _requestFormatHelper = RequestFormatHelper(/* ... */);
        _responseFormatHelper = ResponseFormatHelper(/* ... */);
    }

    void CreateNewTask(Task task)
    {
        // implementation
    }

    void DeleteTask(Task task)
    {
        // implementation
    }

    // rest of methods...
}

Nevertheless, the code works it has several code smells:

  • High cohesion - the relation between objects are really strong. Objects are aggregated and aggregation is the one of strongest types of links between objects.
  • Violation of Open/Closed principle - one of the SOLID object oriented design principles.
  • Lack of testability - if you decide to unit test this code, you will be in problem. Unit testing is supposed to be done in isolation. Having high cohesion code you can’t get required level of isolation. Moreover it is not possible to substitute concrete class with mock object by using some famous JMock, RhinoMocks or Moq.
  • Lack of flexibility - if you decide to change implementation of some of depended objects, say ResponseFormatHelper you would probably change the implementation of ApiAdapter as well.

If you think that you code is not testable or flexible, don’t waste your time.. apply the power of refactoring.

What to do? It is basically very simple to correct such code, you just need to follow this:

  • Always hide the details behind the interface - all behavior objects must conform to particular interface. Other objects must refer to another object only with knowledge of the interface. In terms of programming languages, if you pass object to client code you must always pass it by interface (e.g public SomeAction(IHttpClient client, Type type, Data data);.
  • Use dependency injection for louse coupled code - try to avoid to create depended object by new, inject the dependency by constructor of public property. (e.g public ApiAdapter(IHttpClient client, IRequestFormatHelper requestHelper, IResponseFormatHelper responseHelper).

How the code might look after refactoring:

public interface IHttpClient
{
    void WebCall(/*...*/);
}

public interface IRequestFormatHelper
{
    string Format(/*...*/);
}

public interface IResponseFormatHelper
{
    string Format(/*...*/);
}


public class ApiAdapter
{
    private IHttpClient _client;
    private IRequestFormatHelper _requestFormatHelper;
    private IResponseFormatHelper _responseFormatHelper;

    // ...

    public ApiAdapter(IHttpClient client, IRequestFormatHelper requestFormatHelper, IResponseFormatHelper responseFormatHelper)
    {
        _client = client;
        _requestFormatHelper = requestFormatHelper;
        _responseFormatHelper = requestFormatHelper;
    }

Now, you can easily test this code using mocks, like this simple test:

[Test]
public void CreateNewTask_SendPutRequest_WithJson()
{
    // arrange
    var client = new Mock<IHttpClient>();
    var requestFormatter = new Mock<IRequestFormatHelper>();
    var responseFormater = new Mock<responseFormatHelper>();

    // act 
    var api = new ApiAdapter(client, requestFormatter, responseFormater);
    api.CreateTask (new Task());

    // arrange
    client.Verify(client.WebCall(/*...*/)).Called().WithArguments(/*...*/;
}

And change any details of injected objects, without any changes in ApiAdapter. Refactoring to testablity is important, as much testable code you got as much flexible code you got.