diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs
index 125a72f85d..b5acc046c2 100644
--- a/TUnit.Core/TestContext.cs
+++ b/TUnit.Core/TestContext.cs
@@ -242,6 +242,15 @@ public string GetDisplayName()
return _cachedDisplayName;
}
+ ///
+ /// Clears the cached display name, forcing it to be recomputed on next access.
+ /// This is called after discovery event receivers run to ensure custom argument formatters are applied.
+ ///
+ internal void InvalidateDisplayNameCache()
+ {
+ _cachedDisplayName = null;
+ }
+
public Dictionary ObjectBag => _testBuilderContext.ObjectBag;
public bool ReportResult { get; set; } = true;
diff --git a/TUnit.Engine/Building/TestBuilder.cs b/TUnit.Engine/Building/TestBuilder.cs
index 0545329b93..11ed1b0075 100644
--- a/TUnit.Engine/Building/TestBuilder.cs
+++ b/TUnit.Engine/Building/TestBuilder.cs
@@ -802,6 +802,11 @@ public async Task BuildTestAsync(TestMetadata metadata,
await InvokeDiscoveryEventReceiversAsync(context);
+ // Clear the cached display name after discovery events
+ // This ensures that ArgumentDisplayFormatterAttribute and similar attributes
+ // have a chance to register their formatters before the display name is finalized
+ context.InvalidateDisplayNameCache();
+
return test;
}
diff --git a/TUnit.TestProject/ArgumentDisplayFormatterTests.cs b/TUnit.TestProject/ArgumentDisplayFormatterTests.cs
new file mode 100644
index 0000000000..c02c7f0d14
--- /dev/null
+++ b/TUnit.TestProject/ArgumentDisplayFormatterTests.cs
@@ -0,0 +1,72 @@
+using TUnit.TestProject.Attributes;
+
+namespace TUnit.TestProject;
+
+[EngineTest(ExpectedResult.Pass)]
+public class ArgumentDisplayFormatterTests
+{
+ [Test]
+ [MethodDataSource(nameof(Data1))]
+ [ArgumentDisplayFormatter]
+ public async Task FormatterShouldBeAppliedToMethodDataSource(Foo foo)
+ {
+ // Verify the formatter was applied by checking the display name
+ var displayName = TestContext.Current!.GetDisplayName();
+ await Assert.That(displayName).IsEqualTo("FormatterShouldBeAppliedToMethodDataSource(FooFormatterValue)");
+ }
+
+ [Test]
+ [Arguments(1, 2, 3)]
+ [ArgumentDisplayFormatter]
+ public async Task FormatterShouldBeAppliedToArguments(int a, int b, int c)
+ {
+ // Verify the formatter was applied by checking the display name
+ var displayName = TestContext.Current!.GetDisplayName();
+ await Assert.That(displayName).IsEqualTo("FormatterShouldBeAppliedToArguments(INT:1, INT:2, INT:3)");
+ }
+
+ [Test]
+ [MethodDataSource(nameof(DataWithException))]
+ [ArgumentDisplayFormatter]
+ public async Task FormatterShouldPreventExceptionInToString(Bar bar)
+ {
+ // The Bar.ToString() throws, but the formatter should prevent that
+ var displayName = TestContext.Current!.GetDisplayName();
+ await Assert.That(displayName).IsEqualTo("FormatterShouldPreventExceptionInToString(BarFormatterValue)");
+ }
+
+ public static IEnumerable Data1() => [new Foo()];
+
+ public static IEnumerable DataWithException() => [new Bar()];
+}
+
+public class Foo
+{
+ public override string ToString() => throw new Exception("Foo.ToString should not be called");
+}
+
+public class Bar
+{
+ public override string ToString() => throw new Exception("Bar.ToString should not be called");
+}
+
+public class FooFormatter : ArgumentDisplayFormatter
+{
+ public override bool CanHandle(object? value) => value is Foo;
+
+ public override string FormatValue(object? value) => "FooFormatterValue";
+}
+
+public class BarFormatter : ArgumentDisplayFormatter
+{
+ public override bool CanHandle(object? value) => value is Bar;
+
+ public override string FormatValue(object? value) => "BarFormatterValue";
+}
+
+public class IntFormatter : ArgumentDisplayFormatter
+{
+ public override bool CanHandle(object? value) => value is int;
+
+ public override string FormatValue(object? value) => $"INT:{value}";
+}
diff --git a/global.json b/global.json
index 2e2218a904..08c5374eff 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,8 @@
{
"sdk": {
- "version": "10.0.100-rc.1.25451.107",
- "rollForward": "latestFeature"
+ "version": "9.0.305",
+ "rollForward": "latestMajor",
+ "allowPrerelease": true
},
"test": {
"runner": "Microsoft.Testing.Platform"