Last time, I wrote a follow-up to this article about whether or not you should unit test an abstract class. Now, I’d like to write a small follow-up to that follow-up 😊
To recap, here are the main points from the article and the previous follow-up:
-
You shouldn’t unit test abstract classes directly because they are implementation details.
-
Test them indirectly, via the concrete classes that inherit from the abstract class. That’s because tests should view domain classes as a black box. From the perspective of a unit test, it doesn’t matter whether those classes share their logic or duplicate it. What matters is the observable behavior.
-
If you write a library and the abstract class is meant for the external consumption, create a derived class yourself and test that class’s behavior.
-
These points are about abstract classes specifically. If you have a non-abstract base class, it’s fine to unit test it directly.
Someone also proposed to create an abstract base unit test class and have concrete test classes inherit from it.
So, assuming we have the following class hierarchy:
public abstract class Person { public string Name { get; set; } public string GetSignature() { return $"Regards,\r\n{Name}"; } } public class Student : Person { /* Other methods */ } public class Professor : Person { /* Other methods */ }
we could create an abstract PersonTests
class:
public abstract class PersonTests { [Fact] public void Every_person_has_a_signature() { // Arrange Person person = CreatePerson("Name"); // Act string personName = person.Name; // Assert Assert.Equal("Name", personName); } protected abstract Person CreatePerson(string name); }
and then the StudentTests
and ProfessorTests
would both run this test against their own version of Person
:
public class StudentTests : PersonTests { protected override Person CreatePerson(string name) { return new Student(name); } /* Student-specific tests */ } public class ProfessorTests : PersonTests { protected override Person CreatePerson(string name) { return new Professor(name); } /* Professor-specific tests */ }
Such an implementation looks appealing because it allows us to avoid test duplication. All subclasses of Person
essentially get the Every_person_has_a_signature
test for free.
And still, I don’t recommend that you do that. This approach violates one of the principles I brought up above: that tests should view domain classes as a black box.
The introduction of PersonTests
leaves too much knowledge of the production code’s internal structure. At the same time, you don’t need to duplicate the test either. A pragmatic approach here would be to add this test once to either StudentTests
or ProfessorTests
.
This solution is not ideal, of course, but it gives a better trade-off than coupling your tests to the production code’s implementation details (the base abstract class, that is).
--Vlad
Enjoy this message? Here are more things you might like:
Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.
Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »
My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »