NUnit – generic classes tests

NUnit – generic classes tests

nunit_generic_classes_tests

Some times ago I faced a task to write tests for generic classes. In the simplest approach it is quite easy task. At the beginning let’s assume that class which we want to test implement following interface:

public interface ISerializer<T>
{
    T Deserialize(string text);

    string Serialize(T obj);
}

If we want to test this class using for example NUnit library we can simply write a few test cases. In our case we decide to write 2 basic tests.

[Test]
public void ShouldSerializeAndDeserializeCorrectly()
{
    var serializer = new Serializer<Cat>();
    var obj = new Cat();

    var serialized = serializer.Serialize(obj);
    var deserialized = serializer.Deserialize(serialized);

    Assert.AreEqual(deserialized, obj);
}

[Test]
public void ShouldDeserializeWithErrorCorrectly()
{
    var serializer = new Serializer<Cat>();
    var obj = new Cat();

    var serialized = serializer.Serialize(obj);
    var deserialized = serializer.Deserialize(serialized + "=");

    Assert.AreEqual(deserialized, obj);
}

As you can see this kind of tests is easy to understand and very simple at all. In this case it is the best choice. However how do you think it will work when we would have a dozen implementation of our interface and we would want to test them all? Or we want to test some implementation with a several classes as a generic parameter. Or imagine that we want both: many implementations tested with many classes. Now of test cases would grow very fast. Ad it could be quite hart to maintain that amount of similar code.

Testing for different type parameters

To achieve this we can use a NUnit functionality named TestCaseSource. It gives us a possibility to define our test cases dynamically.

[TestFixture]
public class TestClass
{
    public static IEnumerable<IGenericTestCase> TestCases()
    {
        yield return new GenericTestCase<Cat>();
        yield return new GenericTestCase<Dog>();
    }

    [Test]
    [TestCaseSource("TestCases")]
    public void ShouldSerializeAdnDeserializeCorrectly(IGenericTestCase testCase)
    {
        testCase.ShouldDeserializeWithErrorCorrectly();
    }

    [Test]
    [TestCaseSource("TestCases")]
    public void ShouldDeserializeWithErrorCorrectly(IGenericTestCase testCase)
    {
        testCase.ShouldDeserializeWithErrorCorrectly();
    }
}

As you can see, we can add a parameters to our tests define how this parameter will be populated using a TestCaseSource attribute. In static method we can create test cases dynamically. But let’s look what is a test parameter. We pass an interface to tests, but we define a specific typed objects into our source data. It looks as follow:

public interface IGenericTestCase
{
    void ShouldSerializeAdnDeserializeCorrectly();

    void ShouldDeserializeWithErrorCorrectly();
}


public class GenericTestCase<T> : IGenericTestCase
where T : new()
{
    public void ShouldSerializeAdnDeserializeCorrectly()
    {
        var serializer = new Serializer<T>();
        var obj = new T();

        var serialized = serializer.Serialize(obj);
        var deserialized = serializer.Deserialize(serialized);

        Assert.AreEqual(deserialized, obj);
    }

    public void ShouldDeserializeWithErrorCorrectly()
    {
        var serializer = new Serializer<T>();
        var obj = new T();

        var serialized = serializer.Serialize(obj);
        var deserialized = serializer.Deserialize(serialized + "=");

        Assert.AreEqual(deserialized, obj);
    }
}

Test login into GenericTestCase is exactly the same as in the previous basic tests. That is correct because the logic is the same. The only thing that we want change is a way of generating test cases.

This method is also very easy but it is the most beneficial if you really want to test generic class in many dimensions.

Probably you noticed that above generic test case support only change of type parameter but it is not a problem to support also different implementations. We have to change just a few places.

public class GenericTestCase<T, TImpl> : IGenericTestCase
    where T : new()
    where TImpl : ISerializer<T>, new()
{
    public void ShouldSerializeAdnDeserializeCorrectly()
    {
        var serializer = new TImpl();
        // ...
    }

    public void ShouldDeserializeWithErrorCorrectly()
    {
        var serializer = new TImpl();
        // ...
    }
}

I hope these examples will help you to understand TestCases mechanism in NUnit.

Photo source