Creating (default) test instances of a class, without exposing default constructor.

Did you ever need to just have an instance of a class you cannot instantiate because the default constructor is not available? Do you want to create a test instance just to be used in unit tests? Don’t want to break up the design of your code just for testing? This post might help you:

Sample code:


public MyClass {
final int someField;

private MyClass() {
// may not use this
someField = -1;
}

public MyClass(int someFieldValue) {
someField = someFieldValue;
}

int getSomeField() {
return someField;
}
}

So lets say this class is used in a lot of places. And all we want is have a default test instance. We don’t care about the internals. Lets say we only use it to toss around using an interface which we mock in our tests. Like so:


public void someUnitTestUsingMyClassAndMockingAnInterface() {
MyClass instance = new MyClass(-1); // this works, but its not really descriptive
MyClass someotherMyClass = new MyClass(-1); // like, what is -1? we don't want to be bothered by this
EasyMock.expect(someinterface.doSomethingWithMyClass(instance)).andReturn(someotherMyClass);
...
}

There are two ways to approach this problem:

  • Make private constructor public
  • Use a Factory or a creation method

So, just making the default constructor public sounds easy and is used very often. And, because we want to let our fellow developers know it is just to be used in unit-tests we place a comment in them. This mostly results in something like this:


public MyClass {
final int someField;

public MyClass() {
// Use this only in tests!
someField = -1;
}

}

However, this has a few drawbacks:

  • You cannot see if the default constructor should only be used for tests, unless you look into the code itself and read that comment line
  • You broke with the initial design decision to have only objects instantiated with a given “someField”

So how do we prevent this?

We use a factory, or a creation method. Lets start with the first, the factory: If there is a factory, just use it to get a test instance. If the factory lacks a ‘createTestInstance()’ method, then add it. Like so:

public MyClassFactoryImpl implements MyClassFactory {

public MyClass createTestInstance() {
return new MyClass(-1);
}

}

We could discuss about wether you want the method to be factory method to be static here, and so forth, but the idea is clear.

However, when there is no factory present it is unlikely you want to create a factory just for your unit tests. In that case I suggest you use the creation method, also refered to as the factory method pattern. Simply add a public static method that returns a new instance. The final result would be:

public MyClass {
final int someField;

public static MyClass createTestInstance() {
return new MyClass();
}

private MyClass() {
// may not use this
someField = -1;
}

public MyClass(int someFieldValue) {
someField = someFieldValue;
}

int getSomeField() {
return someField;
}
}

The benefits are clear:

  • The default constructor remains private, so the design is left untouched
  • It is clear what the createTestInstance method does. No comments needed.

And in your unit test (using EasyMock as well) it looks like this:


public void someUnitTestUsingMyClassAndMockingAnInterface() {
MyClass instance = MyClass.createTestInstance();
MyCLass someotherMyClass = MyClass.createTestInstance();
EasyMock.expect(someinterface.doSomethingWithMyClass(instance)).andReturn(someotherMyClass);
...
}

Note: When you wind up with multiple create methods you should consider using the Factory pattern instead. Yes, creating a factory is the best thing once creation logic starts to dominate your class. (ie, think of the Single Responsibility Principle).
Note 2: If you’re a design purist, you might even debate that the public constructor of MyClass should be a creation method. It improves consistency as all instances are created by creation methods. It also has the benefit of describing what kind of instance you get (ie, what the field means). Such improvements are valid and should be made. I consider these improvements small refactorings. They should never be undervalued.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.