seattleSummer
seattleSummer

Reputation: 53

Unit test on legacy code, object creation

I am adding unit tests to an existing application. The class relationship is like this:

class TopClass creates class A and B and common class AB;
class A creates class A1, A2, A3, A4 and AB;
class B creates class B1 and AB;

I am told to pass all depended objects (as interface) when creating the top level object in order to do dependency injection. So class Constructors need to be changed as this:

TopClass(A a, A1 a1, A2 a2, A3 a3, A4 a4, AB ab, B b, B1 b1)
A(A1 a1, A2 a2, A3 a3, A4 a4, AB ab)
B(B1 b1, AB ab)

Is it the right/good way? Is there a way that no need to create all objects at the beginning of the application?

Upvotes: 2

Views: 117

Answers (3)

Jeff Bowman
Jeff Bowman

Reputation: 95714

Short answer: Yes, that's where you start, but it's not necessarily a good ending point. TopClass should care only about (interfaces of) A, B, and AB. The implementations and dependencies of those objects are not relevant to TopClass. This also means that mocking TopClass should be very simple: You need a mock A, a mock B, and a mock AB, and you can pass those in directly.

How do you create an A without caring about A's dependencies? Instead of an A instance, receive a Provider<A>, where Provider is Java's built-in no-argument factory interface.

At that point, your constructors look as follows:

TopClass(A a, B b, AB ab)
A(A1 a1, A2 a2, A3 a3, A4 a4, AB ab)
B(B1 b1, AB ab)

Where any of those types could be replaced with a Provider<Type> if you want to delay creation until you need it, or if you need more than one.


"But wait!" I hear you cry. "Doesn't that mean I have to make a complicated series of boilerplate Providers in order to hide the implementation/dependency details from TopClass?" Yes, it does, and you can do that manually--or you can use a dependency injection framework like Spring, Guice, or Dagger.

A manual one would look something like this:

public class EntryPoint() {
  AB ab = new AB();

  Provider<TopClass> provideTopClass() {
    return new Provider<TopClass>() {
      @Override public TopClass get() {
        return new TopClass(provideA().get(), provideB().get(), provideAB().get());
      }
    };
  }

  Provider<A> provideA() {
    return new Provider<AB>() {
      @Override public AB get() {
        return new A(provideA1().get(), provideA2.get(), ...);
      }
    };
  }

  Provider<AB> provideAB() {
    return new Provider<AB>() {
      @Override public AB get() {
        return ab;   // always reuse the same instance if you'd like
      }
    };
  }

  // and so forth
}

There, you've just extracted all of the creation and wiring into one class, which Guice can emulate at run-time and which Dagger can generate directly at compile-time. You'll need to work with your team to choose a framework you want, or start manually, but overall this should provide a lot of rewards in making an easy-to-assemble and easy-to-test application.

Upvotes: 1

karfau
karfau

Reputation: 667

If you create the instances in this order:

AB ab = new AB();//and so on for all classes wihtout dependencies like a1, a2, ..., b1
A a = new A(a1, a2, a3, a4, ab)
B b = new B(b1, ab)
TopClass(a, b, ab)

But as you say, that TopClass creates instances of those classes, that is the actual problem. it should expect instances, not create them. Maybe this way it doesn't even need the ab parameter if it only uses it for creating A and B.

Using some Dependency Injection Framework/Library can be one way of taking a lot of boilerplate away from the developer, but its costs like

  • efford for changing code to make it work in application and tests
  • runtime performance (depending on the framework)

should be considered.

Upvotes: 0

dkatzel
dkatzel

Reputation: 31658

No, that is not a good way.

If possible, you should only have to pass in interface A and interface B

TopClass(A a, B b)

This way unit tests for TopClass only worry about its direct dependencies and not dependencies of dependencies. This will also let you mock out implementations of A and B in your tests.

Then for tests of Ayou might have a constructor that passes in A1, A2 etc but TopClass shouldn't know about that.

Upvotes: 2

Related Questions