From 466c3534c050fedeb04307e255a986568ea60d92 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 19:25:02 +0000
Subject: [PATCH 1/4] Initial plan
From 83080fd8c4e5e69cf5769e6cd0cec8fb4b22ddd3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 19:40:49 +0000
Subject: [PATCH 2/4] Update DisplayNameAttribute to support class-level usage
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
---
.../TestMetadata/DisplayNameAttribute.cs | 2 +-
.../ClassDisplayNameAttributeTests.cs | 16 ++++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
create mode 100644 TUnit.TestProject/ClassDisplayNameAttributeTests.cs
diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
index fb4b4cccff4..101e314328a 100644
--- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
+++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
@@ -28,7 +28,7 @@ namespace TUnit.Core;
///
/// The display name template. Can include parameter placeholders in the format of "$parameterName".
///
-[AttributeUsage(AttributeTargets.Method, Inherited = false)]
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute
{
///
diff --git a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
new file mode 100644
index 00000000000..007e73e0c6f
--- /dev/null
+++ b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
@@ -0,0 +1,16 @@
+using TUnit.TestProject.Attributes;
+
+namespace TUnit.TestProject;
+
+[EngineTest(ExpectedResult.Pass)]
+[DisplayName("Custom Class Display Name")]
+public class ClassDisplayNameAttributeTests
+{
+ [Test]
+ public async Task Test()
+ {
+ // This test should inherit the class display name as a prefix or part of the test name
+ await Assert.That(TestContext.Current!.GetDisplayName())
+ .DoesNotContain("ClassDisplayNameAttributeTests");
+ }
+}
\ No newline at end of file
From 59052c2c6b27106ea5a0ecea79ab979890da0619 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 3 Aug 2025 19:52:31 +0000
Subject: [PATCH 3/4] Enhance DisplayNameAttribute to support class-level
parameters and improve documentation
Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com>
---
.../TestMetadata/DisplayNameAttribute.cs | 36 +++++++++++++++----
.../ClassDisplayNameAttributeTests.cs | 15 ++++++++
2 files changed, 45 insertions(+), 6 deletions(-)
diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
index 101e314328a..f9cb349aee1 100644
--- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
+++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
@@ -5,28 +5,35 @@
namespace TUnit.Core;
///
-/// Attribute that allows specifying a custom display name for a test method.
+/// Attribute that allows specifying a custom display name for a test method or test class.
///
///
///
-/// This attribute can be applied to test methods to provide more descriptive names than the default method name.
+/// This attribute can be applied to test methods or test classes to provide more descriptive names than the default method or class name.
///
///
/// The display name can include parameter placeholders in the format of "$parameterName" which will be
-/// replaced with the actual parameter values during test execution. For example:
+/// replaced with the actual parameter values during test execution. For test methods, method parameters
+/// will be used for substitution. For test classes, constructor parameters will be used for substitution. For example:
///
/// [Test]
/// [Arguments("John", 25)]
/// [DisplayName("User $name is $age years old")]
/// public void TestUser(string name, int age) { ... }
+///
+/// [Arguments("TestData")]
+/// [DisplayName("Class with data: $data")]
+/// public class MyTestClass(string data) { ... }
///
///
///
-/// When this test runs, the display name would appear as "User John is 25 years old".
+/// When these tests run, the display names would appear as "User John is 25 years old" and
+/// "Class with data: TestData" respectively.
///
///
///
/// The display name template. Can include parameter placeholders in the format of "$parameterName".
+/// For methods, method parameter names can be referenced. For classes, constructor parameter names can be referenced.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute
@@ -38,17 +45,34 @@ protected override string FormatDisplayName(DiscoveredTestContext context)
var mutableDisplayName = displayName;
- var parameters = testDetails
+ // Try to substitute method parameters first
+ var methodParameters = testDetails
.MethodMetadata
.Parameters
.Zip(testDetails.TestMethodArguments, (parameterInfo, testArgument) => (ParameterInfo: parameterInfo, TestArgument: testArgument));
- foreach (var parameter in parameters)
+ foreach (var parameter in methodParameters)
{
mutableDisplayName = mutableDisplayName.Replace($"${parameter.ParameterInfo.Name}",
ArgumentFormatter.Format(parameter.TestArgument, context.ArgumentDisplayFormatters));
}
+ // If there are still placeholders and we have class parameters, try to substitute them
+ if (mutableDisplayName.Contains('$') && testDetails.TestClassArguments.Length > 0)
+ {
+ var classParameters = testDetails
+ .MethodMetadata
+ .Class
+ .Parameters
+ .Zip(testDetails.TestClassArguments, (parameterInfo, testArgument) => (ParameterInfo: parameterInfo, TestArgument: testArgument));
+
+ foreach (var parameter in classParameters)
+ {
+ mutableDisplayName = mutableDisplayName.Replace($"${parameter.ParameterInfo.Name}",
+ ArgumentFormatter.Format(parameter.TestArgument, context.ArgumentDisplayFormatters));
+ }
+ }
+
return mutableDisplayName;
}
}
diff --git a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
index 007e73e0c6f..2641c0d2812 100644
--- a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
+++ b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
@@ -13,4 +13,19 @@ public async Task Test()
await Assert.That(TestContext.Current!.GetDisplayName())
.DoesNotContain("ClassDisplayNameAttributeTests");
}
+}
+
+[EngineTest(ExpectedResult.Pass)]
+[Arguments("TestValue")]
+[DisplayName("Class with parameter: $value")]
+public class ClassDisplayNameWithParametersTests(string value)
+{
+ [Test]
+ public async Task Test()
+ {
+ // This test should show the class display name with parameter substitution
+ var displayName = TestContext.Current!.GetDisplayName();
+ await Assert.That(displayName)
+ .Contains("TestValue");
+ }
}
\ No newline at end of file
From 18bb0e93084c6980dfae4d6449d9b8c02c33176e Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Sun, 3 Aug 2025 22:41:41 +0100
Subject: [PATCH 4/4] Update public API snapshots after rebasing on main
---
...Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt | 2 +-
...Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt | 2 +-
.../Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt | 2 +-
global.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt
index bc50475c2f8..8e495d0073f 100644
--- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt
+++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt
@@ -577,7 +577,7 @@ namespace
public DiscoveryResult() { }
public static .DiscoveryResult Empty { get; }
}
- [(.Method, Inherited=false)]
+ [(.Class | .Method, Inherited=false)]
public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute>
{
public DisplayNameAttribute(string displayName) { }
diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt
index 31044dbadbb..02039171a27 100644
--- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt
+++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt
@@ -577,7 +577,7 @@ namespace
public DiscoveryResult() { }
public static .DiscoveryResult Empty { get; }
}
- [(.Method, Inherited=false)]
+ [(.Class | .Method, Inherited=false)]
public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute>
{
public DisplayNameAttribute(string displayName) { }
diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt
index 33f833323fc..0dce58cb703 100644
--- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt
+++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt
@@ -531,7 +531,7 @@ namespace
public DiscoveryResult() { }
public static .DiscoveryResult Empty { get; }
}
- [(.Method, Inherited=false)]
+ [(.Class | .Method, Inherited=false)]
public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute>
{
public DisplayNameAttribute(string displayName) { }
diff --git a/global.json b/global.json
index 39667c87d8a..3f28e42f5e1 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "9.0.303",
+ "version": "9.0.302",
"rollForward": "latestFeature"
}
}