NUnit TestCase with Generics

NUnit test methods actually can be generic as long as the generic type arguments can be inferred from parameters:

[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(T instance)
{
    Console.WriteLine(instance);
}

NUnit Generic Test

If the generic arguments cannot be inferred, the test runner will not have a clue how to resolve type arguments:

[TestCase(42)]
[TestCase("string")]
[TestCase(double.Epsilon)]
public void GenericTest<T>(object instance)
{
    Console.WriteLine(instance);
}

NUnit Generic Test Fail

But in this case you can implement a custom attribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseGenericAttribute : TestCaseAttribute, ITestBuilder
{
    public TestCaseGenericAttribute(params object[] arguments)
        : base(arguments)
    {
    }

    public Type[] TypeArguments { get; set; }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (!method.IsGenericMethodDefinition)
            return base.BuildFrom(method, suite);

        if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
        {
            var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
            parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
            return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
        }

        var genMethod = method.MakeGenericMethod(TypeArguments);
        return base.BuildFrom(genMethod, suite);
    }
}

Usage:

[TestCaseGeneric("Some response", TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]
public void MyMethod_GenericCall_MakesGenericCall<T1, T2>(string expectedResponse)
{
    // whatever
}

And a similar customization for TestCaseSourceAttribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TestCaseSourceGenericAttribute : TestCaseSourceAttribute, ITestBuilder
{
    public TestCaseSourceGenericAttribute(string sourceName)
        : base(sourceName)
    {
    }

    public Type[] TypeArguments { get; set; }

    IEnumerable<TestMethod> ITestBuilder.BuildFrom(IMethodInfo method, Test suite)
    {
        if (!method.IsGenericMethodDefinition)
            return base.BuildFrom(method, suite);

        if (TypeArguments == null || TypeArguments.Length != method.GetGenericArguments().Length)
        {
            var parms = new TestCaseParameters { RunState = RunState.NotRunnable };
            parms.Properties.Set("_SKIPREASON", $"{nameof(TypeArguments)} should have {method.GetGenericArguments().Length} elements");
            return new[] { new NUnitTestCaseBuilder().BuildTestMethod(method, suite, parms) };
        }

        var genMethod = method.MakeGenericMethod(TypeArguments);
        return base.BuildFrom(genMethod, suite);
    }
}

Usage:

[TestCaseSourceGeneric(nameof(mySource)), TypeArguments = new[] { typeof(IMyInterface), typeof(MyConcreteClass) }]

I had occasion to do something similar today, and wasn't happy with using reflection.

I decided to leverage [TestCaseSource] instead by delegating the test logic as a test context to a generic testing class, pinned on a non-generic interface, and called the interface from individual tests (my real tests have many more methods in the interface, and use AutoFixture to set up the context):

class Sut<T>
{
    public string ReverseName()
    {
        return new string(typeof(T).Name.Reverse().ToArray());
    }
}

[TestFixture]
class TestingGenerics
{
    public static IEnumerable<ITester> TestCases()
    {
        yield return new Tester<string> { Expectation = "gnirtS"};
        yield return new Tester<int> { Expectation = "23tnI" };
        yield return new Tester<List<string>> { Expectation = "1`tsiL" };
    }

    [TestCaseSource("TestCases")]
    public void TestReverse(ITester tester)
    {
        tester.TestReverse();
    }

    public interface ITester
    {
        void TestReverse();
    }

    public class Tester<T> : ITester
    {
        private Sut<T> _sut;

        public string Expectation { get; set; }

        public Tester()
        {
            _sut=new Sut<T>();
        }

        public void TestReverse()
        {
            Assert.AreEqual(Expectation,_sut.ReverseName());
        }

    }
}