diff --git a/src/Verify.TUnit.Tests/ParamsArrayTests.ParamsArray.verified.txt b/src/Verify.TUnit.Tests/ParamsArrayTests.ParamsArray.verified.txt new file mode 100644 index 0000000000..01a4fa5c96 --- /dev/null +++ b/src/Verify.TUnit.Tests/ParamsArrayTests.ParamsArray.verified.txt @@ -0,0 +1 @@ +6 \ No newline at end of file diff --git a/src/Verify.TUnit.Tests/ParamsArrayTests.cs b/src/Verify.TUnit.Tests/ParamsArrayTests.cs new file mode 100644 index 0000000000..ba74078770 --- /dev/null +++ b/src/Verify.TUnit.Tests/ParamsArrayTests.cs @@ -0,0 +1,10 @@ +public class ParamsArrayTests +{ + // TestMethodArguments holds the raw pre-binding arguments, which TUnit bundles into + // the params array at invocation time. So the argument count does not match the + // parameter count and the arguments cannot be used for snapshot naming. + [Test] + [Arguments(1, 2, 3)] + public Task ParamsArray(params int[] values) => + Verify(values.Sum()); +} diff --git a/src/Verify.TUnit/Verifier.cs b/src/Verify.TUnit/Verifier.cs index 033d2275d1..225cccef37 100644 --- a/src/Verify.TUnit/Verifier.cs +++ b/src/Verify.TUnit/Verifier.cs @@ -20,6 +20,20 @@ static Task AddFile(string path) public static void AddAttachmentEvents() => VerifierSettings.AddTestAttachment(AddFile); + // TestContext.Current is null outside a test, for example in a + // [Before(TestSession)] hook or in code that does not flow the test context. + // Dereferencing it there gives a bare NullReferenceException. + internal static TestDetails CurrentTestDetails() + { + var context = TestContext.Current; + if (context is null) + { + throw new("TestContext.Current is null. Verify can only be used from within a test method."); + } + + return context.Metadata.TestDetails; + } + public static InnerVerifier BuildVerifier(string sourceFile, VerifySettings settings, bool useUniqueDirectory = false, int lineNumber = 0) { Guards.AgainstBadSourceFile(sourceFile); @@ -28,16 +42,24 @@ public static InnerVerifier BuildVerifier(string sourceFile, VerifySettings sett settings.UseUniqueDirectory(); } - var details = TestContext.Current!.Metadata.TestDetails; + var details = CurrentTestDetails(); var type = details.MethodMetadata.Class.Type; var classArguments = details.TestClassArguments; var methodArguments = details.TestMethodArguments; + var parameterNames = details.GetParameterNames(); if (!settings.HasParameters && (classArguments.Length > 0 || methodArguments.Length > 0)) { - settings.SetParameters([.. classArguments, .. methodArguments]); - settings.SetClassArgumentCount(classArguments.Length); + // Only apply when the argument count matches the parameter count. A params + // array exposes raw pre-binding arguments, which TUnit bundles at invocation + // time, so the counts differ and parameterized snapshot naming would throw. + // MSTest and XunitV3 apply the same guard. + if (classArguments.Length + methodArguments.Length == parameterNames?.Count) + { + settings.SetParameters([.. classArguments, .. methodArguments]); + settings.SetClassArgumentCount(classArguments.Length); + } } VerifierSettings.AssignTargetAssembly(type.Assembly); @@ -49,7 +71,7 @@ public static InnerVerifier BuildVerifier(string sourceFile, VerifySettings sett settings, type.NameWithParent(), method.Name, - details.GetParameterNames(), + parameterNames, pathInfo, lineNumber); } diff --git a/src/Verify.TUnit/VerifyChecks.cs b/src/Verify.TUnit/VerifyChecks.cs index 090d8bd0bd..219852b551 100644 --- a/src/Verify.TUnit/VerifyChecks.cs +++ b/src/Verify.TUnit/VerifyChecks.cs @@ -5,7 +5,7 @@ public static class VerifyChecks { public static Task Run() { - var details = TestContext.Current!.Metadata.TestDetails; + var details = Verifier.CurrentTestDetails(); var type = details.MethodMetadata.Class.Type; VerifierSettings.AssignTargetAssembly(type.Assembly); return InnerVerifyChecks.Run(type.Assembly);