Add explicit generic type specification to TestCase - #4620
Conversation
| "version": "8.0.100", | ||
| "allowPrerelease": false, | ||
| "rollForward": "disable" | ||
| "rollForward": "latestFeature" |
There was a problem hiding this comment.
You can set "rollForward": "latestPatch" to allow 8.0.1xx.
Now it also allows 8.0.2xx and MS is has in the past broken things when adding new "features". (6.0.300)
But I haven't found anything breaking with 8.0.200
There was a problem hiding this comment.
Thanks! I hadn't realized there was a precedent there.
39e9e95 to
93355d8
Compare
| public string? ExcludePlatform { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Comma-delimited list of platforms to run the test for |
There was a problem hiding this comment.
Note to self: Fix this copy/paste error
There was a problem hiding this comment.
Yes, copy it from TestFixtureAttribute's TypeArgs property instead.
manfred-brands
left a comment
There was a problem hiding this comment.
See comments requesting a practical example where current code without TypeArgs won't work, but it does now.
| public string? ExcludePlatform { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Comma-delimited list of platforms to run the test for |
There was a problem hiding this comment.
Yes, copy it from TestFixtureAttribute's TypeArgs property instead.
| { | ||
| } | ||
|
|
||
| [TestCase("doesn't work", TypeArgs = new[] { typeof(int) })] |
There was a problem hiding this comment.
I feel an Analyzer rule request coming up.
There was a problem hiding this comment.
True, analyzers are a great validator for what would otherwise be runtime checks. What do you think about also having a generic attribute to extend this on modern (NET8+) runtimes after we get the framework targeting that TFM?
There was a problem hiding this comment.
Do we really need to wait for .NET8? It is a C#11 feature and it seems to work in .NET6.0 but fails in .NET Framework. with a runtime error: Generic types are not valid
/// <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) };
}
}And equivalent version with 2, 3, 4, etc. parameters.
With a usage :
[TestCase(2, TypeArgs = new[] { typeof(double) }, ExpectedResult = typeof(double))]
[TestCase<double>(2, ExpectedResult = typeof(double))]
public Type GenericMethodAndParameterWithExplicitOrImplicitTyping<T>(T input)
=> typeof(T);
| Assert.That(typeof(T), Is.EqualTo(typeof(int))); | ||
| } | ||
|
|
||
| [TestCase(2, TypeArgs = new[] { typeof(int) })] |
There was a problem hiding this comment.
Doesn't this already without having to add TypeArgs as the TryGetTypeArguments determines the types from the actual parameters?
Maybe add a [TestCase(2)] as well to proof it still works.
|
Thanks for syncing your changes here @OsirisTerje @manfred-brands as I've been revisiting the test scenarios, I've realized it's likely very minimal extra work to make this work for TestCaseSource - something Charlie had also originally advocated for in the issue. |
| /// If not set explicitly, the generic types will be inferred | ||
| /// based on the test case parameters. | ||
| /// </summary> | ||
| public Type[]? TypeArgs { get; set; } = null; |
There was a problem hiding this comment.
I was surprised at just how little was required to make it work for TestCaseSource after TestCase was done. If I had made this public originally instead of private then it would've "just worked"
manfred-brands
left a comment
There was a problem hiding this comment.
Some of the tests are wrong and asserting failures that are the result of that instead of what really should be happening.
| var exception = Assert.Throws<NUnitException>(() => test.Method!.Invoke(test.Parent, test.Arguments)); | ||
|
|
||
| Assert.That(exception.InnerException, Has.Message.EqualTo("Object does not match target type.")); |
There was a problem hiding this comment.
This is wrong. The test now fails because test.Parent is of type ParameterizedMethodSuite and not TestCaseSourceAttributeFixture
The error talks about target type which is the this argument, not about parameters.
You have to call TestBuilder.RunTest and check the results which shows that the test fails.
However, in case of mismatch of type arguments, the test is marked as non-runnable as you don't have to run it at al.
There was a problem hiding this comment.
Sorry, maybe there's been a misunderstanding on my end. I like your suggestion to use RunTest() as it avoids the need to explicitly check exception status. I'll push some changes shortly.
However, in case of mismatch of type arguments, the test is marked as non-runnable as you don't have to run it at al.
I may still not be understanding this suggestion. Are you saying that mismatched values and generic types should fail earlier than test execution time? If so, I think I'm having a hard time reconciling that with "let the (dotnet) runtime sort it out" when TypeArgs are explicitly passed.
There was a problem hiding this comment.
Are you saying that mismatched values and generic types should fail earlier than test execution time
Yes. If NUnit cannot determine the TypeArgs. See NUnitTestCaseBuilder. If NUnit cannot determine the TypeArguments to use, the test is not runnable.
"let the (dotnet) runtime sort it out" when TypeArgs are explicitly passed.
If TypeArgs is passed, the above TryGetTypeArguments is not called and NUnit first try to do some conversion in TypeHelper.ConvertArgumentList but after that passes it on to the runtime. The conversions done by NUnit are the ones normally done by the compiler when passing an int to a double parameter.
…ine if it fails to run
manfred-brands
left a comment
There was a problem hiding this comment.
Changes and tests look fine now.
I'll leave it up to you if you want to add the Generic TestCaseAttributes
|
Thanks @manfred-brands ! I'm going to merge this now for release -related timing (FYI @OsirisTerje ) and will create those follow up issues later today |

Fixes #1215
Adds support for explicitly specifying a generic type for a test case method. I've chosen to keep it simple and let the runtime itself ensure that cases like this work or not:
I'd like to hear thoughts on if we wanted to allow the framework to eagerly detect and mark these as
NonRunnable. My own suggestion is to propose we defer it to another PR