Unit Testing – How Far to Go with Dependency Injection and Mocking

dependency-injectionmockingtestingunit testing

Say I have a following class:

public class A {

  public void execute() {
    if (something) 
      ThirdPartyApi.method();   
  }
}

Now, I would like to test in particular the execute() method. I want to assure that all paths are covered and work as expected. However, the issue I would run into here, is not being able to mock the ThirdPartyApi because it's not provided as a dependency. An easy fix to this is to provide the ThirdPartyApi as a dependency like this.

public class A {

  private ThirdPartyApi api;

  public A(ThirdPartyApi api) {
    this.api = api;
  }

  public void execute() {
    if (something) 
      api.method();   
  }
}

Now I would get the functionality I want and everyone would be happy. However, what I don't like here is the fact that I need to provide all internal dependencies through a constructor/set methods in order to make my class fully testable and independent of any external dependencies (Android API, libraries, utils etc.).

This becomes a problem for dependencies that are not directly needed for accomplishing the use case of the class. They are a mere tool e.g unit conversion tool. This way I request from the user of this class to provide me a dependency that I can internally create on my own. It makes the API harder to use, because the user must know more about the class than they should/need to.

To make the example more specific, say the ThidPartyApi is a huge external unit conversion library. Why would a user need to provide me a class from this library through a constructor? It's something that's a mere side tool used to accomplish the use case. It's not directly related to the use case itself. Requiring this dependency in a constructor would make the API more confusing. See following class:

public class Storage {

  private ConversionTool api;

  public Storage (ConversionTool api) {
    this.api = api;
  }

  public void storeInteger(int a) {
    storeString(api.convertToString(a));
  }
}

vs

public class Storage {

  private ConversionTool api;

  public Storage () {
    this.api = new ConversionTool();
  }

  public void storeInteger(int a) {
    storeString(api.convertToString(a));
  }
}

What would be the preferred approach? Providing side dependencies through the constructor makes it more testable and mockable, but the API loses on its meaning. Not providing side dependencies through the constructor keeps the meaning to a class, but I lose the ability to mock everything inside the class and fully test the class.

Best Answer

Why would a user need to provide me a class from this library through a constructor? It's something that's a mere side tool used to accomplish the use case.

IMHO this is not the really important question. If ThirdPartyApi is a "side tool" does not matter. Instead, I would recommend to ask

  • does not mocking ThirdPartyApi let you still write simple, stable and fast tests?

If the answer is "yes", then there is no need for injecting ThirdPartyApi into your class (at least not for testing), if the answer is "no", DI will be the better approach.

Your example of a "conversion library" looks like one where the answer could be "yes", just like you typically would not start mocking build-in conversion functions of your programming language or standard library. But if constructing a ThirdPartyApi object requires something like a database connection over the network, because several conversion function need data from a database, then injecting it could be the better approach.

Note also that there might be other reasons for making ThirdPartyApi injectable than justs tests. For example, because of avoiding too much vendor-specific coupling, or because you want to have different "conversion strategies".

DI is not an end in itself, it is a means to and end. Use it when you need it, not "just in case".