Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If I use Mockito do I even need Guice?

I've been learning about Dependency Injection (e.g. Guice) and it seems to me one of the main drivers, testability, is covered already quite nicely by Mocking (e.g. Mockito). Difference between Dependency Injection and Mocking framework (Ninject vs RhinoMock or Moq) is a nice summary of commonality between Dependency Injection and Mockito but it doesn't offer guidance on which to use when they overlap in capability.

I'm about to design an API and I'm wondering if I should:

A] Use Mockito only

B] Use Guice and design two implementation of interfaces--one for real and one for testing

C] Use Mockito AND Guice together--if so, how?

I'm guessing the right answer is C, to use them both, but I'd like some words of wisdom: where can I use either Dependency Injection or Mocking, which should I choose and why?

like image 546
Michael Osofsky Avatar asked Nov 29 '22 23:11

Michael Osofsky


1 Answers

Guice and Mockito have very different and complementary roles, and I'd argue that they work best together.

Consider this contrived example class:

public class CarController {
  private final Tires tires = new Tires();
  private final Wheels wheels = new Wheels(tires);
  private final Engine engine = new Engine(wheels);
  private Logger engineLogger;

  public Logger start() {
    engineLogger = new EngineLogger(engine, new ServerLogOutput());
    engine.start();
    engineLogger.recordEvent(ENGINE_STARTED);
    return engineLogger;
  }
}

Notice how much extra work this class does: You don't actually use your Tires or Wheels other than to create a working Engine, and there's no way to substitute your Tires or Wheels: Any car, in production or in test, must have real Tires, real Wheels, a real Engine, and a real Logger that really logs to a server. Which part do you write first?

Let's make this class DI-friendly:

public class CarController { /* with injection */
  private final Engine engine;
  private final Provider<Logger> loggerProvider;
  private Logger engineLogger;

  /** With Guice, you can often keep the constructor package-private. */
  @Inject public Car(Engine engine, Provider<Logger> loggerProvider) {
    this.engine = engine;
    this.loggerProvider = loggerProvider
  }

  public Logger start() {
    engineLogger = loggerProvider.get();
    engine.start();
    engineLogger.recordEvent(ENGINE_STARTED);
    return engineLogger;
  }
}

Now the CarController doesn't have to concern itself with the tires, wheels, engine, or log output, and you can substitute in whichever Engine and Logger you'd like by passing them into the constructor. In this way, DI is useful in production: With the change of a single module, you can switch your Logger to log to a circular buffer or local file, or switch to a supercharged Engine, or upgrade to SnowTires or RacingTires separately. This also makes the class more testable, because now substituting out implementations becomes much easier: you can write your own test doubles such as FakeEngine and DummyLogger and put them in your CarControllerTest. (Of course, you can also create setter methods or alternate constructors, and you can design the class in this way without actually using Guice. Guice's power comes from constructing large dependency graphs in a loosely-coupled way.)

Now, for those test doubles: In a world with only Guice but no Mockito, you would have to write your own Logger-compatible test double and your own Engine-compatible test double:

public class FakeEngine implements Engine {
  RuntimeException exceptionToThrow = null;
  int callsToStart = 0;
  Logger returnLogger = null;

  @Override public Logger start() {
    if (exceptionToThrow != null) throw exceptionToThrow;
    callsToStart += 1;
    return returnLogger;
  }
}

With Mockito, that becomes automatic, with better stack traces and many more features:

@Mock Engine mockEngine;
// To verify:
verify(mockEngine).start();
// Or stub:
doThrow(new RuntimeException()).when(mockEngine).start();

...and that's why they work so well together. Dependency injection gives you the chance to write a component (CarController) without concerning yourself with its dependencies' dependencies (Tires, Wheels, ServerLogOutput), and to change dependency implementations at your will. Then Mockito lets you create these replacement implementations with minimal boilerplate, which can be injected wherever and however you'd like.

Side note: Neither Guice nor Mockito should be a part of your API, as you mention in the question. Guice can be a part of your implementation details, and may be a part of your constructor strategy; Mockito is a part of your testing and shouldn't have any effect on your public interface. Nevertheless, the choice of frameworks for OO design and testing is an excellent discussion to have before starting your implementation.


Update, incorporating comments:

  • Typically, you won't actually use Guice in unit tests; you'll call the @Inject constructors manually with the assortment of objects and test doubles you prefer. Remember that it's easier and cleaner to test state rather than interactions, so you'll never want to mock data objects, you'll almost always want to mock remote or asynchronous services, and that expensive and stateful objects might be better represented with lightweight fakes. Don't be tempted to over-use Mockito as the only solution.

  • Mockito has its own "dependency injection" feature called @InjectMocks, which will replace the system-under-test's fields with @Mock fields of the same name/type even if there are no setters. This is a nice trick to replace dependencies with mocks, but as you noted and linked, it will fail silently if dependencies are added. Given that downside, and given that it misses out on much of the design flexibility that DI provides, I've never had a need to use it.

like image 119
Jeff Bowman Avatar answered Dec 05 '22 12:12

Jeff Bowman