A custom ArgumentDisplayFormatter is being ignored. Here's a minimal reproduction with a failing test because of it (here's a repo with the repro):
public class Tests
{
[Test]
[MethodDataSource(nameof(Data1))]
[ArgumentDisplayFormatter<FooFormatter>]
public void Test1(Foo foo)
{
}
public IEnumerable<Foo> Data1() => [new()];
}
public class Foo
{
public override string ToString() => throw new Exception("Foo.ToString");
}
public class FooFormatter : ArgumentDisplayFormatter
{
public override bool CanHandle(object? value) => true;
public override string FormatValue(object? value) => "FooFormatterValue";
}
The test fails because the FooFormatter isn't being used and the Foo.ToString() method gets called and throws:
PS D:\Projects\others\TUnitBugs> dotnet test
Restore complete (0.4s)
TUnitBugs succeeded (0.2s) → TUnitBugs\bin\Debug\net9.0\TUnitBugs.dll
TUnitBugs test failed with 2 error(s) (0.4s)
D:\\Projects\\others\\TUnitBugs\\TUnitBugs\\Tests.cs(3): error test failed:
Test1 (0ms): InvalidOperationException: Failed to expand data source for test 'Test1': Foo.ToString
D:\Projects\others\TUnitBugs\TUnitBugs\bin\Debug\net9.0\TUnitBugs.dll : error run failed: Tests failed: 'D:\Projects\others\TUnitBugs\TUnitBugs\bin\Debug\net9.0\TestResults\TUnitBugs_net9.0_x64.log' [net9.0|x64]
Test summary: total: 1, failed: 1, succeeded: 0, skipped: 0, duration: 0.3s
Build failed with 2 error(s) in 1.1s
I found that this happens because the argument display value is computed before the formatter is added.
The argument display value is being created here:
|
// Create the test object BEFORE invoking event receivers |
|
// This ensures context.InternalExecutableTest is set for error handling in registration |
|
var creationContext = new ExecutableTestCreationContext |
|
{ |
|
TestId = testId, |
|
DisplayName = context.GetDisplayName(), |
|
Arguments = testData.MethodData, |
|
ClassArguments = testData.ClassData, |
|
Context = context, |
|
TestClassInstanceFactory = testData.TestClassInstanceFactory, |
|
ResolvedMethodGenericArguments = testData.ResolvedMethodGenericArguments, |
|
ResolvedClassGenericArguments = testData.ResolvedClassGenericArguments |
|
}; |
Which happens before the events are executed for the test later:
|
await InvokeDiscoveryEventReceiversAsync(context); |
And ArgumentDisplayFormatterAttribute depends on having it's OnTestDiscovered executed so it could add the argument formatter.
I discovered this, because I have a class that throws for the default argument formatter, so I'm using a custom one to make sure my tests don't fail. Except right now the tests are failing anyways.
A custom
ArgumentDisplayFormatteris being ignored. Here's a minimal reproduction with a failing test because of it (here's a repo with the repro):The test fails because the
FooFormatterisn't being used and theFoo.ToString()method gets called and throws:I found that this happens because the argument display value is computed before the formatter is added.
The argument display value is being created here:
TUnit/TUnit.Engine/Building/TestBuilder.cs
Lines 771 to 783 in 6ed6416
Which happens before the events are executed for the test later:
TUnit/TUnit.Engine/Building/TestBuilder.cs
Line 803 in 6ed6416
And
ArgumentDisplayFormatterAttributedepends on having it'sOnTestDiscoveredexecuted so it could add the argument formatter.I discovered this, because I have a class that throws for the default argument formatter, so I'm using a custom one to make sure my tests don't fail. Except right now the tests are failing anyways.