0

I want to unit test a class A. This class is cascading with other classes, so class A creates an instance of class B and class B of class C. Like that:

public class A {
    public B b;
    public A() {
        this.b = new B();
    }
}
 
public class B {
    public C c;
    public B() {
        this.c = new C();
    }
}
 

If now class C reads in its constructor any external input, like a file or properties, how could I avoid that? I don‘t see a way how you would mock it, but isn‘t there a possibility to keep external factors out of other classes too while testing?

hem
  • 1,012
  • 6
  • 11
Mike Kng
  • 255
  • 2
  • 11

4 Answers4

4

The problem you're running into here is that your classes are tightly coupled to the specific implementations of other classes, so your "unit" is forced to be testing the behavior of all of those classes together.

The typical way to avoid this is to use Dependency Injection, making your classes get coupled with interfaces instead of specific classes. Then you can mock the injected interfaces to unit test a single class's behavior.

public class AImpl {
    public B b;
    public AImpl(B b) {
        this.b = b;
    }
}

public interface B {
    // methods
}

public class BImpl implements B {
    public C c;
    public BImpl (C c) {
        this.c = c;
    }
}

When you create an AImpl in production code, you now have to provide it with a specific C implementation. You can do this with new AImpl(new BImpl(new CImpl)) or you can use a dependency injection framework like Spring to figure all those details out for you.

When you're unit testing, you can create a mock or a stub to have exactly the behavior you want the B to exhibit for that particular test, and pass that stub in to the constructor: new AImpl(myMockedB).

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
3

You should not create instance of B in class A - instead you should provide instance B when you are creating A - it's called Inversion of Control. Popular way to do it is with dependency injection, for example with Spring framework.

Then everything becomes simple - when your class needs to read a file with FileReader class, for tests you can create a 'fake' implementation, for example when you call FileReader.readFile(), your fake implementation will simply return hardcoded String, or Stream, depends what you want.

This concept is really big, imagine when your FileReader is actually a DatabaseReader or ExternalServiceCaller. Instead of testing real database in unit tests (good luck with that), you create a FakeDatabaseReader that operates on regular Java HashMap and everything is easy to test.

Or when your code deals with times/dates, imagine a functionality that does something only at 29th February - you could wait for 4 years to test it, or instead provide a Clock object to your test that is set to particular date. The Clock class has static methods offering a variety of alternative clocks, to provide a fixed moment, a moment adjusted into the future or the past, or a clock with an altered cadence.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Shadov
  • 5,421
  • 2
  • 19
  • 38
1

Fist all this class is not designed to be tested gently since you can't inject class B, notice that since you have any new into a class is pretty similar to hardcode the instance.

The appropriate way should be like

  public class A {
    public B b;
    public A(B b) {
        this.b = b;
    }
}

Notice that in that way you can instantiate the class like

A a = new A(b); 

In that way, b could be a mock or dummy or stub or any object for test purpose. If you are facing a legacy code you should refactor it to make it able to be tested. If it's your design you should refactor ASAP to make it testable.

Inversion of control and dependency injection should help to archive what you wanna do.

cehdmoy
  • 46
  • 5
0

Not that I know about, you take care of such external factors, or ensure during the development of class A, B or C itself that this kind of problems do not arise during testing.

But if you do not have control on sources of class A, B or C, then I think you are stuck!

Siva
  • 598
  • 3
  • 11