DEV Community

Cover image for How to Write NUnit Tests for Many Classes with Generic Test Fixtures
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

How to Write NUnit Tests for Many Classes with Generic Test Fixtures

We previously looked at how we can run a single written test with multiple data. By adding parameters and using the TestCase attribute, we were able to convert one test into many (from the test runner’s point of view) without explicitly writing more code. In this week’s article, we’ll take this concept and apply it while zoomed out – instead of substituting test arguments, we’ll be interchanging object types.

An Example Use Case

Let’s assume we’re building a record-keeping system for a university. We want to be able to store details for the staff and students who attend. When designing the data model, we add the following classes. They each have properties to capture the first and last names of the people they represent. The two names are kept as separate data entities for maximum flexibility, but each class also has a method to consistently combine them together. To avoid over-complicating this example, each class only has one unique distinguishing property.

public class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string CourseProgramme { get; set; }

    public string GetFullName()
    {
        return $"{FirstName} {LastName}";
    }
}

public class Staff
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Department { get; set; }

    public string GetFullName()
    {
        return $"{FirstName} {LastName}";
    }
}
Enter fullscreen mode Exit fullscreen mode

We’ve avoided using class inheritance as we want to keep the data models simple and flexible. However, there will be use cases where we’ll want to process records regardless of their type. To achieve this, we add the following interface.

public interface IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string GetFullName();
}
Enter fullscreen mode Exit fullscreen mode

And apply it in Student and Staff.

public class Student : IPerson
{
    ...
Enter fullscreen mode Exit fullscreen mode
public class Staff : IPerson
{
    ...
Enter fullscreen mode Exit fullscreen mode

This introduces one small problem: the implementation of GetFullName is duplicated across both classes. In this case, having identical copies of the code isn’t the issue: the logic is trivial, so we aren’t too concerned about maintenance (e.g. if we wanted the full name to be formatted differently). We do however want both implementations to produce consistent results.

Luckily, we don’t need to achieve this by having a single piece of logic to combine first and last names. (There’s nothing to stop us from doing so, but the overhead outweighs the benefit in this situation.) Instead, we can write tests to check GetFullName generates the same output when first and last names are set appropriately in both classes.

Writing the Test

Let’s start by writing a test for Student. In the following example, we compare the result of GetFullName with an expected value.

public class GetFullNameTests
{
    [Test]
    public void FullNameContainsFirstAndLastName()
    {
        // Arrange

        var person = new Student
        {
            FirstName = "First",
            LastName = "Last"
        };

        // Act

        var fullName = person.GetFullName();

        // Assert

        Assert.That(fullName, Is.EqualTo("First Last"));

    }
}
Enter fullscreen mode Exit fullscreen mode

We want GetFullName to have the same behaviour in the Staff class too (though the way it’s achieved doesn’t need to be a carbon copy). One of the most obvious ways to add coverage for this would be to copy and paste the test in the previous example, changing Student for Staff; this would result in two almost identical tests. However, our code would be tidier if we could parameterise the object type we want created – especially as we’re checking for the same behaviour.

Generic test fixtures let us do this. As shown in the following example, we specify the desired type as an argument for the TestFixture attribute we decorate our test class with. You might notice we haven’t used the TestFixture attribute until now. That’s because it has been optional since NUnit 2.5, except for generic or parameterised tests.

[TestFixture(typeof(Staff))]
[TestFixture(typeof(Student))]
public class GetFullNameTests<T> where T : IPerson, new()
{
    [Test]
    public void FullNameContainsFirstAndLastName()
    {
        // Arrange

        var person = new T
        {
            FirstName = "First",
            LastName = "Last"
        };

        // Act

        var fullName = person.GetFullName();

        // Assert

        Assert.That(fullName, Is.EqualTo("First Last"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

You sometimes write tests for identical behaviour across multiple classes. Where there’s a common type, you might be able to do this with generic test fixtures instead of duplicating individual tests.

A generic test class can have its data type/s specified by passing them as arguments when applying the TestFixture attribute. Depending on the test, you might be able to use this technique to verify certain behaviours across different classes without explicitly writing a test for each data type. In turn, you’ll be able to expand your test coverage quickly, cleanly, and with relatively little effort.


Thanks for reading!

This article is from my newsletter. If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox (once per week), plus bonus developer tips too!

Top comments (0)