Skip to content

Commit

Permalink
Generic test case attribute (nunit#4755)
Browse files Browse the repository at this point in the history
* Add support for generic TestCaseAttribute

* Add support for generic TestCaseData
  • Loading branch information
manfred-brands authored Jul 12, 2024
1 parent 6d6a424 commit 09dcd3a
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 2 deletions.
87 changes: 87 additions & 0 deletions src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,91 @@ public IEnumerable<TestMethod> BuildFrom(IMethodInfo method, Test? suite)

#endregion
}

#if NET6_0_OR_GREATER // Although this compiles for .NET Framework, it fails at runtime with a NotSupportedException : Generic types are not valid.

#pragma warning disable CS3015 // Type has no accessible constructors which use only CLS-compliant types

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T argument)
: base(new object?[] { argument })
{
TypeArgs = new[] { typeof(T) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T1, T2> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T1 argument1, T2 argument2)
: base(new object?[] { argument1, argument2 })
{
TypeArgs = new[] { typeof(T1), typeof(T2) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T1, T2, T3> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3)
: base(new object?[] { argument1, argument2, argument3 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T1, T2, T3, T4> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3, T4 argument4)
: base(new object?[] { argument1, argument2, argument3, argument4 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T1, T2, T3, T4, T5> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3, T4 argument4, T5 argument5)
: base(new object?[] { argument1, argument2, argument3, argument4, argument5 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) };
}
}

#pragma warning restore CS3015 // Type has no accessible constructors which use only CLS-compliant types
#endif
}
78 changes: 78 additions & 0 deletions src/NUnitFramework/framework/TestCaseData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,82 @@ public IgnoredTestCaseData Ignore(string reason)

#endregion
}

#if NET6_0_OR_GREATER // Although this compiles for .NET Framework, it fails at runtime with a NotSupportedException : Generic types are not valid.

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
public class TestCaseData<T> : TestCaseData
{
/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T argument)
: base(new object?[] { argument })
{
TypeArgs = new[] { typeof(T) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
public class TestCaseData<T1, T2> : TestCaseData
{
/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T1 argument1, T2 argument2)
: base(new object?[] { argument1, argument2 })
{
TypeArgs = new[] { typeof(T1), typeof(T2) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
public class TestCaseData<T1, T2, T3> : TestCaseData
{
/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T1 argument1, T2 argument2, T3 argument3)
: base(new object?[] { argument1, argument2, argument3 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
public class TestCaseData<T1, T2, T3, T4> : TestCaseData
{
/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T1 argument1, T2 argument2, T3 argument3, T4 argument4)
: base(new object?[] { argument1, argument2, argument3, argument4 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
public class TestCaseData<T1, T2, T3, T4, T5> : TestCaseData
{
/// <summary>
/// Construct a TestCaseData with a list of arguments.
/// </summary>
public TestCaseData(T1 argument1, T2 argument2, T3 argument3, T4 argument4, T5 argument5)
: base(new object?[] { argument1, argument2, argument3, argument4, argument5 })
{
TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) };
}
}
#endif
}
11 changes: 10 additions & 1 deletion src/NUnitFramework/testdata/TestCaseAttributeFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ public void MethodWithArrayArguments(object o)
}

[TestCase("doesn't work", TypeArgs = new[] { typeof(int) })]
public static void MethodWithIncompatibleTypeArgs<T>(T input)
public static void MethodWithIncompatibleTypeArgs<T>(T _)
{
}

[TestCase(2.0)]
#if NET6_0_OR_GREATER
[TestCase<double>(2)]
[TestCase<double>(2.0)]
#endif
public static void MethodWithoutTypeArgsWithIncompatibleParameters(string _)
{
}
}
Expand Down
82 changes: 81 additions & 1 deletion src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -766,9 +766,66 @@ public void ExplicitTypeArgsWithUnrelatedParameters<T>(string input)
[TestCase(2L, TypeArgs = new[] { typeof(long) }, ExpectedResult = typeof(long))]
[TestCase(2, ExpectedResult = typeof(int))]
[TestCase(2L, ExpectedResult = typeof(long))]
public Type GenericMethodAndParameterWithExplicitOrImplicitTyping<T>(T input)
[TestCase(2, TypeArgs = new[] { typeof(double) }, ExpectedResult = typeof(double))]
public Type GenericMethodAndParameterWithExplicitOrImplicitTyping<T>(T _)
=> typeof(T);

#if NET6_0_OR_GREATER
[TestCase<double>(2)]
[TestCase<double>(2.0)]
public void ExplicitGenericTypeArgsWithCompatibleParameters<T>(T input)
{
Assert.That(input, Is.InstanceOf<T>());
}

[TestCase<int, double>(2, 2.0)]
public void ExplicitGenericTypeArgsWithCompatibleParameters<T1, T2>(T1 input1, T2 input2)
{
Assert.Multiple(() =>
{
Assert.That(input1, Is.InstanceOf<T1>());
Assert.That(input2, Is.InstanceOf<T2>());
});
}

[TestCase<string, int, double>("2", 2, 2.0)]
public void ExplicitGenericTypeArgsWithCompatibleParameters<T1, T2, T3>(T1 input1, T2 input2, T3 input3)
{
Assert.Multiple(() =>
{
Assert.That(input1, Is.InstanceOf<T1>());
Assert.That(input2, Is.InstanceOf<T2>());
Assert.That(input3, Is.InstanceOf<T3>());
});
}

[TestCase<bool, string, int, double>(true, "2", 2, 2.0)]
public void ExplicitGenericTypeArgsWithCompatibleParameters<T1, T2, T3, T4>(T1 input1, T2 input2, T3 input3, T4 input4)
{
Assert.Multiple(() =>
{
Assert.That(input1, Is.InstanceOf<T1>());
Assert.That(input2, Is.InstanceOf<T2>());
Assert.That(input3, Is.InstanceOf<T3>());
Assert.That(input4, Is.InstanceOf<T4>());
});
}

[TestCase<bool, char, string, int, double>(true, 'N', "2", 2, 2.0)]
public void ExplicitGenericTypeArgsWithCompatibleParameters<T1, T2, T3, T4, T5>(T1 input1, T2 input2, T3 input3, T4 input4, T5 input5)
{
Assert.Multiple(() =>
{
Assert.That(input1, Is.InstanceOf<T1>());
Assert.That(input2, Is.InstanceOf<T2>());
Assert.That(input3, Is.InstanceOf<T3>());
Assert.That(input4, Is.InstanceOf<T4>());
Assert.That(input5, Is.InstanceOf<T5>());
});
}

#endif

[Test]
public void ExplicitTypeArgsWithUnassignableParametersFailsAtRuntime()
{
Expand All @@ -786,6 +843,29 @@ public void ExplicitTypeArgsWithUnassignableParametersFailsAtRuntime()
Assert.That(result.Message, Does.Contain("Object of type 'System.String' cannot be converted to type 'System.Int32'."));
}

[Test]
public void MethodWithoutTypeArgsWithIncompatibleParametersFailsAtRuntime()
{
var suite = TestBuilder.MakeParameterizedMethodSuite(
typeof(TestCaseAttributeFixture),
nameof(TestCaseAttributeFixture.MethodWithoutTypeArgsWithIncompatibleParameters));

Assert.Multiple(() =>
{
for (int i = 0; i < suite.TestCaseCount; i++)
{
var test = (Test)suite.Tests[i];
Assert.That(test.RunState, Is.EqualTo(RunState.Runnable));
var result = TestBuilder.RunTest(test);
Assert.That(result.FailCount, Is.EqualTo(1));
Assert.That(result.Message, Does.Contain("Object of type 'System.Double' cannot be converted to type 'System.String'."));
}
});
}

[TestCase(2, TypeArgs = new[] { typeof(long) })]
public void ExplicitTypeArgsWithGenericConstraintSatisfied<T>(int input)
where T : IConvertible
Expand Down
Loading

0 comments on commit 09dcd3a

Please sign in to comment.