How to Write NUnit Tests for Many Classes with Generic Test Fixtures
Tags: csharp, dotnet, testing, tutorial
Published: 2023-11-01 13:00:00

We previously looked at how we can [run a single written test with multiple data](https://webdeveloperdiary.substack.com/p/parameterise-tests-to-increase-test-coverage). 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.

``` csharp
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}";
    }
}
```

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.

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

    public string GetFullName();
}
```

And apply it in `Student` and `Staff`.

``` csharp
public class Student : IPerson
{
    ...
```

``` csharp
public class Staff : IPerson
{
    ...
```

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.

``` csharp
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"));

    }
}
```

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](https://docs.nunit.org/articles/nunit/writing-tests/attributes/testfixture.html), except for generic or parameterised tests.

``` csharp
[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"));
    }
}
```

## 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](https://webdeveloperdiary.substack.com/?sd=pf). 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!