Some Thoughts on Constructors and Unit Testing

Friday, December 9th 2011

This article mainly applies to those languages that have some xUnit-like testing library. I will write the examples in C#, but they should port easily to other languages.

Executive Summary (Yes, that means you.)

  1. Do not create convenience constructors.
  2. Use later-than-setup-time creation of objects in unit tests.

Constructors are important

Back in the late 90s during the Rise of Java, I would see APIs like this.

1
2
3
4
5
6
7
8
9
10
public class AwesomeClass {
public AwesomeClass() {...}
public AwesomeClass(int value) {...}
public AwesomeClass(String name) {...}
public AwesomeClass(Date inaugural) {...}
public AwesomeClass(int value, String name) {...}
public AwesomeClass(int value, Date inaugural) {...}
public AwesomeClass(String name, Date inaugural) {...}
public AweomseClass(int value, String name, Date inaugural) {...}
}

I love me some combinatorial explosions. BOOM!

Ok, I don’t like that. When I want an instance of an object, the constructor communicates to me the expectations that the class has. If a no-argument constructor exists, then that tells me the class can take care of itself. The proliferation of every available combination of constructor parameters as seen above reduces the clarity with which the class’ design can communicate its requirements. And, those requirements should affect the behavior of the class. Otherwise, why have constructor arguments at all?

Constructors in testable/injectable code

When we start using “advanced” programming techniques like dependency injection, the constructor becomes the documentation for the components with which the object fulfills its destined purpose. When we rely on dependency injection containers to construct those objects for us, we have no reason to create the “convenience” constructors found above. We rely on a machine to do the construction.

1
2
3
public class AwesomerClass {
public AwesomerClass(IReflect reflector, ISummarize summarizer) {...}
}

From that class definition, I know that the AwesomerClass requires two arguments to behave in a way appropriate to the system. I know that without an instance of IReflect and ISummarize that I should no have the ability to create an instance of AwesomerClass. It should not make sense.

Testing those DI constructors

The wonder and pain of working with dependency injection means changing the signature of the constructor when new dependencies emerge. For example, let’s say I need a class that sends messages across different transports such as email, Twitter, and faxing. (Yes, Virginia, people still fax.) I start off by writing a test for it.

1
2
3
4
5
6
7
8
9
[TestFixture]
public class SyndicatorTests {
[Test]
public void Syndicator_Requires_Enumerable_Of_Message_Transports() {
TestDelegate td = () => new Syndicator(null);
var ex = Assert.Throws<ArgumentNullException>(td);
Assert.That(ex.ParamName, Is.EqualTo("transporters"));
}
}

Because, after all, a syndicator can’t work if it has no transport over which to send its message.

Then I write write the code to make the test pass.

1
2
3
4
5
6
7
public class Syndicator {
public Syndicator(IEnumerable<ITransportMessages> transporters) {
if(transporters == null) {
throw new ArgumentNullException("transporters");
}
}
}

That’s good. So, now I write a test that ensures that the collection of transports is not empty and the code to make it pass.

1
2
3
4
5
6
7
[Test]
public void Syndicator_Requires_Enumerable_Containing_Transports() {
var empty = Enumerable.Empty<ITransportMessages>();
TestDelegate td = () => new Syndicator(empty);
var ex = Assert.Throws<ArgumentException>(td);
Assert.That(ex.ParamName, Is.EqualTo("transporters"));
}
1
2
3
4
5
6
7
8
9
10
public class Syndicator {
public Syndicator(IEnumerable<ITransportMessages> transporters) {
if(transporters == null) {
throw new ArgumentNullException("transporters");
}
if(transporters.Count() == 0) {
throw new ArgumentException("", "transporters");
}
}
}

And all is well and right in the world. I write 20 other tests exercising the success and expected failures of the Syndicator class. At the end of it, I have a test class that contains the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[TestFixture]
public class SyndicatorTests {
/* 22 Tests */

[SetUp]
public void RunBeforeEachTest() {
this.transporter = MockRepository.GenerateMock<ITransportMessages>();
this.transporters = new List<ITransportMessages> { this.transporter };
this.syndicator = new Syndicator(this.transporters);
}

private ITransportMessages transporter;
private IEnumerable<ITransportMessages>> transporters;
private Syndicator syndicator;
}

Other than the two argument tests at the top of the fixture, the other tests use this.transporter to ensure the proper behavior occurs within syndicator. All is well and good…

…Until the time in the project where we have to add logging to the Syndicator class so that we can troubleshoot errors in the production environment. Darn it! I think to myself. Why didn’t I include that in the first place? Regardless, I have to do it.

Let’s count the number of places that we have to write code to improve this situation.

  1. Both existing constructor tests will now need to include a logging provider that does not trip any exceptions.
  2. A new test ensuring that the logging provider parameter does not contain null.
  3. A new logging provider parameter in the fields.
  4. A new mock creation in the setup.
  5. Passing the new value to the constructor.

Also, we may have to hunt down other places in the test code that creates instances of Sydnicator. I’ve had to do this more than once because of new stories coming in, new dependencies that my class requires.

My testing solution

Whenever I create a new test file for a class that has constructor arguments, I use the following template.

1
2
3
4
5
6
7
8
9
10
11
12
13
[TestFixture]
public class MyClassTests {
[SetUp]
public void RunBeforeEachTest() {
CreateMyClass();
}

private void CreateMyClass() {
this.classToTest = new MyClass();
}

private MyClass classToTest;
}

Then, when I come across a test that adds a new constructor parameter, I add a private field, set it to a reasonable default in the set-up method, set it to an unreasonable value in my constructor test, and use the CreateMyClass method as the TestDelegate. For example, if I were writing that first Syndicator test, it would look like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[TestFixture]
public class SyndicatorTests {
[Test]
public void Syndicator_Requires_Enumerable_Of_Message_Transports() {
this.transporters = null;
TestDelegate td = CreateSyndicator;
var ex = Assert.Throws<ArgumentNullException>(td);
Assert.That(ex.ParamName, Is.EqualTo("transporters"));
}

[SetUp]
public void RunBeforeEachTest() {
this.transporters = new List<ITransportMessages>();
CreateSyndicator();
}

private void CreateSyndicator() {
this.syndicator = new Syndicator(this.transporters);
}

private Syndicator syndicator;
}

I think that this serves my testing purposes much better. I have a ready-to-use Syndicator at any time due to the call in RunBeforeEachTest and I have a centralized TestDelegate in which my only call to new occurs.

After ten years of unit testing, this reoccuring form has emerged as a satisfying and easy-to-maintain organization of test code. I hope that it can help your testing efforts, as well.