Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/CategoryAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,62 @@

namespace TUnit.Core;

/// <summary>
/// Adds a category to a test method, test class, or assembly for organizational and filtering purposes.
/// </summary>
/// <remarks>
/// <para>
/// Categories provide a way to group tests for better organization and selective execution.
/// Tests can be filtered by category when running tests through the TUnit test runner.
/// </para>
///
/// <para>
/// The attribute can be applied at different levels:
/// </para>
/// <list type="bullet">
/// <item>Method level: Categorizes a specific test method</item>
/// <item>Class level: Categorizes all test methods in the class</item>
/// <item>Assembly level: Categorizes all test methods in the assembly</item>
/// </list>
///
/// <para>
/// Multiple categories can be applied to the same target by using multiple instances of this attribute.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// [Category("Integration")]
/// public class IntegrationTests
/// {
/// [Test]
/// [Category("Database")]
/// public void DatabaseTest()
/// {
/// // Test implementation
/// }
///
/// [Test]
/// [Category("API")]
/// [Category("Authentication")]
/// public void ApiAuthenticationTest()
/// {
/// // Test implementation
/// }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class CategoryAttribute(string category) : TUnitAttribute, ITestDiscoveryEventReceiver
{
/// <inheritdoc />
public int Order => 0;

/// <summary>
/// Gets the name of the category.
/// </summary>
public string Category { get; } = category;

/// <inheritdoc />
public void OnTestDiscovery(DiscoveredTestContext discoveredTestContext)
{
discoveredTestContext.AddCategory(Category);
Expand Down
156 changes: 156 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/DependsOnAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,210 @@
namespace TUnit.Core;

/// <summary>
/// Generic version of the <see cref="DependsOnAttribute"/> that specifies a dependency on a test method in a specific class.
/// </summary>
/// <typeparam name="T">The type that contains the test method this test depends on.</typeparam>
/// <remarks>
/// This generic version simplifies specifying dependencies with strong typing, avoiding the need to use <see cref="Type"/> parameters directly.
/// </remarks>
/// <example>
/// <code>
/// // The FeatureTest will only run if UserLoginTest passes
/// [Test]
/// [DependsOn&lt;LoginTests&gt;("UserLoginTest")]
/// public void FeatureTest()
/// {
/// // This test will only run if UserLoginTest in the LoginTests class has passed
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute<T> : DependsOnAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute{T}"/> class.
/// Specifies a dependency on all test methods in the specified class.
/// </summary>
public DependsOnAttribute() : base(typeof(T))
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute{T}"/> class.
/// Specifies a dependency on a specific test method in the specified class.
/// </summary>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
public DependsOnAttribute(string testName) : base(typeof(T), testName)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute{T}"/> class.
/// Specifies a dependency on a specific test method with parameter types in the specified class.
/// </summary>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
/// <param name="parameterTypes">The parameter types of the test method, used to disambiguate overloaded methods.</param>
public DependsOnAttribute(string testName, Type[] parameterTypes) : base(typeof(T), testName, parameterTypes)
{
}
}

/// <summary>
/// Specifies that a test method or class has dependencies on other test methods that must run successfully before this test can execute.
/// </summary>
/// <remarks>
/// <para>
/// Use this attribute when you have tests that depend on the successful execution of other tests.
/// A test decorated with this attribute will only run if all its dependencies have passed.
/// </para>
///
/// <para>
/// Dependencies can be specified by:
/// </para>
/// <list type="bullet">
/// <item>Test method name (when depending on a test in the same class)</item>
/// <item>Test class type (when depending on all tests in another class)</item>
/// <item>Combination of test class type and method name (when depending on a specific test in another class)</item>
/// <item>Test method name and parameter types (when depending on a specific overloaded method)</item>
/// </list>
///
/// <para>
/// For better type safety, use the generic version <see cref="DependsOnAttribute{T}"/> when specifying dependencies on tests in other classes.
/// </para>
///
/// <para>
/// When <see cref="ProceedOnFailure"/> is set to true, the test will run even if its dependencies failed.
/// By default, a test will be skipped if any of its dependencies failed.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// // Simple dependency on a test in the same class
/// [Test]
/// public void FirstTest() { }
///
/// [Test]
/// [DependsOn("FirstTest")]
/// public void SecondTest()
/// {
/// // This test will only run if FirstTest passes
/// }
///
/// // Dependency on a test in another class
/// [Test]
/// [DependsOn(typeof(SetupTests), "Initialize")]
/// public void TestRequiringSetup()
/// {
/// // This test will only run if the Initialize test in SetupTests passes
/// }
///
/// // Dependency with overloaded method disambiguation
/// [Test]
/// [DependsOn("OverloadedTest", new Type[] { typeof(string), typeof(int) })]
/// public void TestWithOverloadDependency() { }
///
/// // Proceeding even if dependency fails
/// [Test]
/// [DependsOn("CriticalTest") { ProceedOnFailure = true }]
/// public void TestThatRunsAnyway()
/// {
/// // This test will run even if CriticalTest fails
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : TUnitAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
/// Specifies a dependency on a test method in the same class.
/// </summary>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
public DependsOnAttribute(string testName) : this(testName, null!)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
/// Specifies a dependency on an overloaded test method in the same class.
/// </summary>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
/// <param name="parameterTypes">The parameter types of the test method, used to disambiguate overloaded methods.</param>
public DependsOnAttribute(string testName, Type[] parameterTypes) : this(null!, testName, parameterTypes)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
/// Specifies a dependency on all test methods in a specific class.
/// </summary>
/// <param name="testClass">The class containing the test methods that must run successfully before this test.</param>
public DependsOnAttribute(Type testClass) : this(testClass, null!)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
/// Specifies a dependency on a specific test method in a specific class.
/// </summary>
/// <param name="testClass">The class containing the test method that must run successfully before this test.</param>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
public DependsOnAttribute(Type testClass, string testName) : this(testClass, testName, null!)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
/// Specifies a dependency on a specific overloaded test method in a specific class.
/// </summary>
/// <param name="testClass">The class containing the test method that must run successfully before this test.</param>
/// <param name="testName">The name of the test method that must run successfully before this test.</param>
/// <param name="parameterTypes">The parameter types of the test method, used to disambiguate overloaded methods.</param>
public DependsOnAttribute(Type testClass, string testName, Type[] parameterTypes)
{
TestClass = testClass;
TestName = testName;
ParameterTypes = parameterTypes;
}

/// <summary>
/// Gets the class containing the test method this test depends on.
/// </summary>
/// <remarks>
/// If null, the dependency is assumed to be on a test in the same class.
/// </remarks>
public Type? TestClass { get; }

/// <summary>
/// Gets the name of the test method this test depends on.
/// </summary>
/// <remarks>
/// If null, the dependency is assumed to be on all tests in the <see cref="TestClass"/>.
/// </remarks>
public string? TestName { get; }

/// <summary>
/// Gets the parameter types of the test method this test depends on.
/// </summary>
/// <remarks>
/// Used to disambiguate overloaded methods with the same name.
/// If null, the first method matching <see cref="TestName"/> will be used.
/// </remarks>
public Type[]? ParameterTypes { get; }

/// <summary>
/// Gets or sets a value indicating whether this test should proceed even if its dependencies have failed.
/// </summary>
/// <remarks>
/// When set to true, the test will run even if its dependencies failed.
/// When set to false (default), the test will be skipped if any of its dependencies failed.
/// </remarks>
public bool ProceedOnFailure { get; set; }

/// <summary>
/// Returns a string representation of the dependency.
/// </summary>
/// <returns>A string that represents the dependency.</returns>
public override string ToString()
{
if (TestClass != null && TestName == null)
Expand Down
25 changes: 25 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,34 @@

namespace TUnit.Core;

/// <summary>
/// Attribute that allows specifying a custom display name for a test method.
/// </summary>
/// <remarks>
/// <para>
/// This attribute can be applied to test methods to provide more descriptive names than the default method name.
/// </para>
/// <para>
/// 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:
/// <code>
/// [Test]
/// [Arguments("John", 25)]
/// [DisplayName("User $name is $age years old")]
/// public void TestUser(string name, int age) { ... }
/// </code>
/// </para>
/// <para>
/// When this test runs, the display name would appear as "User John is 25 years old".
/// </para>
/// </remarks>
/// <param name="displayName">
/// The display name template. Can include parameter placeholders in the format of "$parameterName".
/// </param>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute
{
/// <inheritdoc />
protected override string FormatDisplayName(TestContext testContext)
{
var testDetails = testContext.TestDetails;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,37 @@

namespace TUnit.Core;

/// <summary>
/// Base attribute class that provides functionality for formatting display names of tests in TUnit.
/// Concrete implementations of this class can customize how test names are displayed in test results.
/// </summary>
/// <remarks>
/// This attribute can be applied at the method, class, or assembly level to control test name formatting.
/// Subclasses must implement the <see cref="FormatDisplayName"/> method to define custom formatting logic.
/// The attribute interfaces with the test discovery system to set display names for tests during discovery.
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, Inherited = false)]
public abstract class DisplayNameFormatterAttribute : TUnitAttribute, ITestDiscoveryEventReceiver
{
/// <inheritdoc />
public int Order => 0;

/// <inheritdoc />
public void OnTestDiscovery(DiscoveredTestContext discoveredTestContext)
{
var displayName = FormatDisplayName(discoveredTestContext.TestContext);

discoveredTestContext.SetDisplayName(displayName);
}

/// <summary>
/// When implemented in derived classes, formats the display name for a test.
/// </summary>
/// <param name="testContext">
/// The test context containing information about the test being discovered.
/// </param>
/// <returns>
/// A string containing the formatted display name for the test.
/// </returns>
protected abstract string FormatDisplayName(TestContext testContext);
}
34 changes: 33 additions & 1 deletion TUnit.Core/Attributes/TestMetadata/PropertyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,46 @@

namespace TUnit.Core;

/// <summary>
/// Attaches a custom property with a name-value pair to a test method, test class, or assembly.
/// These properties can be used for filtering tests or for providing additional metadata.
/// </summary>
/// <remarks>
/// Properties added with this attribute are available during test discovery and execution.
/// Multiple properties can be added to the same target by using multiple instances of this attribute.
/// </remarks>
/// <example>
/// <code>
/// [Property("Category", "Integration")]
/// [Property("Owner", "TestTeam")]
/// public class MyIntegrationTests
/// {
/// [Test]
/// [Property("Priority", "High")]
/// public void HighPriorityTest()
/// {
/// // Test implementation
/// }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class PropertyAttribute(string name, string value) : TUnitAttribute, ITestDiscoveryEventReceiver
{
/// <inheritdoc />
public int Order => 0;

/// <summary>
/// Gets the name of the property.
/// </summary>
public string Name { get; } = name;
public string Value { get; } = value;

/// <summary>
/// Gets the value of the property.
/// </summary>
public string Value { get; } = value;

/// <inheritdoc />
public void OnTestDiscovery(DiscoveredTestContext discoveredTestContext)
{
discoveredTestContext.AddProperty(Name, Value);
Expand Down
Loading
Loading