diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
index fb4b4cccff..f9cb349aee 100644
--- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
+++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
@@ -5,30 +5,37 @@
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, Inherited = false)]
+[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.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 7b7c4e0223..1ee2ef7ab6 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
@@ -572,7 +572,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 bde3a8bee3..e76b7d07fd 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
@@ -572,7 +572,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 20cf4713c9..3c88c659a6 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
@@ -526,7 +526,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.TestProject/ClassDisplayNameAttributeTests.cs b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
new file mode 100644
index 0000000000..2641c0d281
--- /dev/null
+++ b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs
@@ -0,0 +1,31 @@
+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");
+ }
+}
+
+[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