12

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.

5 Answers 5

10

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".

4
  • 2
    does not mocking ThirdPartyApi let you still write simple, stable and fast tests? - should be bolded ;)
    – Fabio
    Commented Aug 13, 2018 at 11:59
  • 1
    Thank you for the response, I like your thoughts. This problem appeared for me when trying to test some Android code. I had an internal dependency to NfcManager. In order to be able to write unit tests, I could not have direct Android dependencies. Therefore, I needed to provide this through a constructor, but it didn't make sense to me to expose this in the constructor since I can get it internally. Anyway, this is a very platform specific example. Thank you for the helpful response.
    – aarnaut
    Commented Aug 13, 2018 at 12:05
  • 3
    “Use it when you need it, not "just in case".” this a million times. When starting unit testing and discovering DI, you want to decouple everything and inject everything as dependencies. This increases the complexity of the code a lot. Only use it when you can’t do otherwise such as with a DB connection, or when you want flexibility with the ‘how’ you do it (if you know you’ll convert in multiple ways for example). Keep in mind unit tests without mocks are way easier to write, to read and to maintain. Commented Aug 13, 2018 at 12:07
  • @Fabio: now it is ;-)
    – Doc Brown
    Commented Aug 13, 2018 at 15:38
5

How far should I go with dependency injection and mocking?

The deliberately vague answer is "as far as you need to to produce a good design".

In 1972, Parnas described an interesting principle for module design

at one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.

A third party implementation that we want to elide during testing is a pretty good example of a decision that is "likely to change".

Introducing the capability of providing a specific implementation doesn't necessarily mean that the API affordances for the composition root must expose additional complexity

public Storage () {
    this(new ConversionTool());
}

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

This sort of approach introduces untested code paths, but the untested path satisfies this important constraint: they are "so simple that there are obviously no deficiencies".

The related question is "do we want to elide this third party library during testing?" There are properties that we want our tests to have -- we want to be able to run them often, therefore quickly, therefore in parallel, and without slow side effects. If the third party library makes these properties expensive or impossible, then of course we want to be able to replace the library with a test double.

When the third party library is testable... we may want to replace it anyway. Rainsberger makes a case for this in his talk integrated tests are a scam. TL;DR -- you can test your design more efficiently if you don't have to consider the code path variations in the third party code.

4

A good rule of thumb is that one should avoid mocking pure functions, ie. those whose outputs depend solely on the inputs. There's no need to mock a unit conversion library for the same reason we don't mock out the standard math library or string processing routines.

By contrast, there is a very good reason to mock out disk IO, network traffic, database queries, etc.: they depend on the outside world and have lots of mutable state. The same sequence of calls is not guaranteed to produce the same results (another program could overwrite your file, the user could enable airplane mode, etc.)/ It also enables testing robustness in rare conditions, such as intermittent failures.

However, one reason to mock a pure function would be performance. For example, if you are testing a game, it might be worthwhile to mock the pathfinding so less time is spent running the A* algorithm when it isn't being directly tested.

4

I know I'm coming late to this party but I've got something to say:

Stop using TDD as the only consideration in your design!

It was never meant for this. It's a terrible misunderstanding of what the TDD proponents were saying when they argued that redesigning to make your code testable improves it's design. It does. If you don't screw it up.

Yes, it is possible to turn otherwise easy to construct object graphs into a nightmare to construct. But that's not TDD's fault. There are a few a simple principles that mitigate this complexity.

Convention over configuration is a very simple solution to this problem. It basically says it's OK to hard code default values so long as they are override-able. This advice is gloriously simple to follow in languages that have named parameters and so default arguments. If you're stuck without them, as in Java, you end up needing to simulate them with Joshua Blochs builder pattern if being immutable is important to you. If mutable is fine then setters solve this easily. If you're in C# or Python or such you have named parameters already so stop making excuses.

Another solution is to abstract construction with factories. Doing everything directly in main can become unwieldy quickly. So long as you can think of a good name for it you can pack a factory with hardcoded values that simplify construction while not dictating that this is the only way construction can happen. I like this method because long constructors are much less of a pain if you never have to touch them.

If you're thinking this is to much work just for testing you're absolutely right. I don't do this for testing. I do this because I wan't to preserve my right to change my third party API whenever I damn well please.

To do that I have to be sure I'm not just letting tests mock the library. I have to carefully express only what my domain objects really need handed to them. Nothing more. ThirdPartyLibrary might have 35 useful methods hanging off of it but if I only need two I'm going to make clear I only need those two.

Why? Because if later ThirdPartyLibrary suddenly wants to change it's licence agreement on me I'm not going be held hostage. I'm going to write two stupid methods and move on with my life.

If you let them library's will infiltrate your language space so much that you can't advertise for a job working in your codebase without mentioning the library. Keep that in mind when you decide how to use it.

1

The preferred approach is generally to make an interface for “something to convert stuff”, add that as a dependency, and provide an implementation that uses the third party library. That allows you flexibility in case they cease to exist, charge you too much, or otherwise become unworkable.

Or depending on where it is used, (Storage here) you could consider that class to be the implementation specific to your third party library. But then you can’t unit test it. Sometimes that’s fine because the class isn’t doing much.

In general though, you want to decouple things such that the third party lib only touches one class that you then need to integration test.

Not the answer you're looking for? Browse other questions tagged or ask your own question.