diff --git a/TUnit.Core/Attributes/BaseTestAttribute.cs b/TUnit.Core/Attributes/BaseTestAttribute.cs index 9ba5512753..22b5b808df 100644 --- a/TUnit.Core/Attributes/BaseTestAttribute.cs +++ b/TUnit.Core/Attributes/BaseTestAttribute.cs @@ -1,12 +1,24 @@ namespace TUnit.Core; -// Any new test type attributes should inherit from this -// This ensures we have a location of the test provided by the compiler -// Using [CallerLineNumber] [CallerFilePath] +/// +/// Base class for test method attributes. Automatically captures the source file path and line number +/// where the test is defined, using compiler services. +/// +/// +/// Inherit from this class to create custom test type attributes. The and +/// properties are populated automatically by the compiler via [CallerFilePath] and [CallerLineNumber]. +/// [AttributeUsage(AttributeTargets.Method)] public abstract class BaseTestAttribute : TUnitAttribute { + /// + /// Gets the source file path where the test is defined. + /// public readonly string File; + + /// + /// Gets the line number in the source file where the test is defined. + /// public readonly int Line; internal BaseTestAttribute(string file, int line) diff --git a/TUnit.Core/Attributes/ClassConstructorSourceAttribute.cs b/TUnit.Core/Attributes/ClassConstructorSourceAttribute.cs index d4d89fe7cd..7a8926f882 100644 --- a/TUnit.Core/Attributes/ClassConstructorSourceAttribute.cs +++ b/TUnit.Core/Attributes/ClassConstructorSourceAttribute.cs @@ -3,9 +3,44 @@ namespace TUnit.Core; +/// +/// Specifies a custom to use when creating test class instances. +/// This enables dependency injection and custom object creation for test classes. +/// +/// +/// +/// Can be applied at the class level (affecting only that class) or at the assembly level +/// (affecting all test classes in the assembly). +/// +/// +/// The specified type must implement and have a parameterless constructor. +/// For strongly-typed usage, prefer . +/// +/// +/// +/// +/// [ClassConstructor<DependencyInjectionClassConstructor>] +/// public class MyTests +/// { +/// private readonly IMyService _service; +/// +/// public MyTests(IMyService service) +/// { +/// _service = service; +/// } +/// +/// [Test] +/// public void TestWithInjectedDependency() { } +/// } +/// +/// [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)] public class ClassConstructorAttribute : TUnitAttribute { + /// + /// Initializes a new instance of the class. + /// + /// The type that implements . public ClassConstructorAttribute( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] Type classConstructorType) @@ -13,10 +48,18 @@ public ClassConstructorAttribute( ClassConstructorType = classConstructorType; } + /// + /// Gets or sets the type that implements and is used to create test class instances. + /// [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] public Type ClassConstructorType { get; init; } } +/// +/// Specifies a custom to use when creating test class instances. +/// Generic version that provides compile-time type safety. +/// +/// The type that implements . [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)] public sealed class ClassConstructorAttribute< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods)] T>() diff --git a/TUnit.Core/Attributes/DynamicTestBuilderAttribute.cs b/TUnit.Core/Attributes/DynamicTestBuilderAttribute.cs index 5592b90cad..2caede647b 100644 --- a/TUnit.Core/Attributes/DynamicTestBuilderAttribute.cs +++ b/TUnit.Core/Attributes/DynamicTestBuilderAttribute.cs @@ -3,4 +3,8 @@ namespace TUnit.Core; +/// +/// Marks a method as a dynamic test builder that programmatically generates test cases at runtime. +/// Methods decorated with this attribute can yield test definitions dynamically. +/// public class DynamicTestBuilderAttribute([CallerFilePath] string file = "", [CallerLineNumber] int line = 0) : BaseTestAttribute(file, line); diff --git a/TUnit.Core/Attributes/TUnitAttribute.cs b/TUnit.Core/Attributes/TUnitAttribute.cs index 4a40e36c44..2bf2330764 100644 --- a/TUnit.Core/Attributes/TUnitAttribute.cs +++ b/TUnit.Core/Attributes/TUnitAttribute.cs @@ -1,5 +1,9 @@ namespace TUnit.Core; +/// +/// Base class for all TUnit framework attributes. +/// This class cannot be instantiated directly; use one of the derived attribute types. +/// public class TUnitAttribute : Attribute { internal TUnitAttribute() diff --git a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs index 8ad3d88d69..ca1ad191ce 100644 --- a/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs @@ -32,8 +32,15 @@ namespace TUnit.Core; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public sealed class ArgumentsAttribute : Attribute, IDataSourceAttribute, ITestRegisteredEventReceiver { + /// + /// Gets the array of argument values to pass to the test method. + /// public object?[] Values { get; } + /// + /// Gets or sets a reason to skip this specific test case. + /// When set, the test case will be skipped with the given reason. + /// public string? Skip { get; set; } /// @@ -60,6 +67,10 @@ public sealed class ArgumentsAttribute : Attribute, IDataSourceAttribute, ITestR /// public bool SkipIfEmpty { get; set; } + /// + /// Initializes a new instance of the class with the specified test argument values. + /// + /// The argument values to pass to the test method. Pass null for a single null argument. public ArgumentsAttribute(params object?[]? values) { if (values == null) @@ -112,9 +123,26 @@ public ValueTask OnTestRegistered(TestRegisteredContext context) public int Order => 0; } +/// +/// Provides a strongly-typed inline value for a parameterized test with a single parameter. +/// +/// The type of the test parameter. +/// The value to pass to the test method. +/// +/// +/// [Test] +/// [Arguments<string>("hello")] +/// [Arguments<string>("world")] +/// public void TestWithString(string input) { } +/// +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public sealed class ArgumentsAttribute(T value) : TypedDataSourceAttribute, ITestRegisteredEventReceiver { + /// + /// Gets or sets a reason to skip this specific test case. + /// When set, the test case will be skipped with the given reason. + /// public string? Skip { get; set; } /// diff --git a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs index 9dbfc8ff2c..2d87437f55 100644 --- a/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/ClassDataSourceAttribute.cs @@ -3,6 +3,33 @@ namespace TUnit.Core; +/// +/// Provides test data by creating instances of one or more specified types. +/// The instances are created using their constructors and can optionally be shared across tests. +/// +/// +/// +/// Use this attribute to inject class instances as test method parameters or constructor arguments. +/// The attribute supports sharing instances across tests via the property and +/// keyed sharing via the property. +/// +/// +/// For strongly-typed single-parameter usage, prefer the generic version . +/// +/// +/// +/// +/// // Create a new instance for each test +/// [Test] +/// [ClassDataSource(typeof(MyService))] +/// public void TestWithService(MyService service) { } +/// +/// // Share the instance across all tests in the class +/// [Test] +/// [ClassDataSource(typeof(MyService), Shared = [SharedType.PerClass])] +/// public void TestWithSharedService(MyService service) { } +/// +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public sealed class ClassDataSourceAttribute : UntypedDataSourceGeneratorAttribute { @@ -74,7 +101,16 @@ public ClassDataSourceAttribute(params Type[] types) _types = types; } + /// + /// Gets or sets how instances are shared across tests, one per type parameter. + /// Defaults to (a new instance per test). + /// public SharedType[] Shared { get; set; } = [SharedType.None]; + + /// + /// Gets or sets the sharing keys, one per type parameter. + /// Used when is set to to identify shared instances. + /// public string[] Keys { get; set; } = []; [UnconditionalSuppressMessage("Trimming", "IL2062:The parameter of method has a DynamicallyAccessedMembersAttribute, but the value passed to it can not be statically analyzed.", @@ -121,6 +157,27 @@ public ClassDataSourceAttribute(params Type[] types) } +/// +/// Provides test data by creating an instance of . +/// The instance is created using its constructor and can optionally be shared across tests. +/// +/// The type to instantiate as test data. +/// +/// +/// Use the property to control instance sharing: +/// (default) creates a new instance per test, +/// shares within the test class, +/// shares across the assembly, +/// shares by a specified . +/// +/// +/// +/// +/// [Test] +/// [ClassDataSource<MyService>(Shared = SharedType.PerClass)] +/// public void TestWithService(MyService service) { } +/// +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public sealed class ClassDataSourceAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : DataSourceGeneratorAttribute diff --git a/TUnit.Core/Attributes/TestData/IDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/IDataSourceAttribute.cs index 04835fe5c7..9dd5122933 100644 --- a/TUnit.Core/Attributes/TestData/IDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/IDataSourceAttribute.cs @@ -1,7 +1,17 @@ namespace TUnit.Core; +/// +/// Defines a data source that provides test data for parameterized tests. +/// Implement this interface to create custom data source attributes. +/// public interface IDataSourceAttribute { + /// + /// Asynchronously generates data rows for parameterized tests. + /// Each yielded function, when invoked, produces one set of arguments for a test invocation. + /// + /// Metadata about the test and parameters being generated. + /// An async enumerable of factory functions that produce test data rows. public IAsyncEnumerable>> GetDataRowsAsync(DataGeneratorMetadata dataGeneratorMetadata); /// diff --git a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs index ce19c143dd..503c90f249 100644 --- a/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MatrixDataSourceAttribute.cs @@ -4,6 +4,36 @@ namespace TUnit.Core; +/// +/// Generates test cases from all combinations (Cartesian product) of parameter values. +/// +/// +/// +/// For boolean parameters, all values (true, false) are generated automatically. +/// For enum parameters, all defined enum values are generated automatically. +/// For other types, use [Matrix(...)] on individual parameters to specify the values. +/// +/// +/// Use on the test method to exclude specific combinations. +/// +/// +/// +/// +/// [Test, MatrixDataSource] +/// public void TestAllCombinations( +/// [Matrix(1, 2, 3)] int x, +/// [Matrix("a", "b")] string y) +/// { +/// // Generates 6 test cases: (1,"a"), (1,"b"), (2,"a"), (2,"b"), (3,"a"), (3,"b") +/// } +/// +/// [Test, MatrixDataSource] +/// public void TestWithEnum(bool enabled, MyEnum mode) +/// { +/// // Automatically generates all bool x enum combinations +/// } +/// +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class MatrixDataSourceAttribute : UntypedDataSourceGeneratorAttribute, IAccessesInstanceData { diff --git a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs index 2d896b5aa3..a2fa77b852 100644 --- a/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs +++ b/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs @@ -5,12 +5,57 @@ namespace TUnit.Core; +/// +/// Provides test data from a method, property, or field on the specified type . +/// +/// The type containing the data source member. +/// The name of the method, property, or field that provides the test data. +/// +/// +/// [Test] +/// [MethodDataSource<TestDataProvider>(nameof(TestDataProvider.GetTestCases))] +/// public void MyTest(int input, string expected) { } +/// +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public class MethodDataSourceAttribute< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string methodNameProvidingDataSource) : MethodDataSourceAttribute(typeof(T), methodNameProvidingDataSource); +/// +/// Provides test data from a method, property, or field in the test class or a specified type. +/// +/// +/// +/// The data source can be a method, property, or field that returns test data. +/// Supported return types include single values, , , +/// , tuples, and arrays. +/// +/// +/// When no class type is specified, the data source is looked up in the test class itself. +/// Both static and instance members are supported. +/// +/// +/// +/// +/// // Using a method in the same class +/// [Test] +/// [MethodDataSource(nameof(GetTestData))] +/// public void MyTest(int value, string name) { } +/// +/// public static IEnumerable<(int, string)> GetTestData() +/// { +/// yield return (1, "one"); +/// yield return (2, "two"); +/// } +/// +/// // Using a method in another class +/// [Test] +/// [MethodDataSource(typeof(SharedData), nameof(SharedData.GetValues))] +/// public void MyTest(string value) { } +/// +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] public class MethodDataSourceAttribute : Attribute, IDataSourceAttribute { @@ -20,12 +65,27 @@ public class MethodDataSourceAttribute : Attribute, IDataSourceAttribute | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.FlattenHierarchy; + /// + /// Gets the type containing the data source member, or null if the data source is in the test class itself. + /// [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] public Type? ClassProvidingDataSource { get; } + + /// + /// Gets the name of the method, property, or field that provides the test data. + /// public string MethodNameProvidingDataSource { get; } + /// + /// Gets or sets an AOT-safe factory function for providing test data programmatically. + /// When set, this factory is used instead of reflection-based member lookup. + /// public Func>>>? Factory { get; set; } + /// + /// Gets or sets the arguments to pass to the data source method. + /// Use this when the data source method requires parameters. + /// public object?[] Arguments { get; set; } = []; /// diff --git a/TUnit.Core/Attributes/TestData/SharedType.cs b/TUnit.Core/Attributes/TestData/SharedType.cs index 0bb7f57294..fb05203b8a 100644 --- a/TUnit.Core/Attributes/TestData/SharedType.cs +++ b/TUnit.Core/Attributes/TestData/SharedType.cs @@ -1,10 +1,34 @@ namespace TUnit.Core; +/// +/// Specifies how class data source instances are shared across tests. +/// Used with and . +/// public enum SharedType { + /// + /// A new instance is created for each test (no sharing). This is the default. + /// None, + + /// + /// The instance is shared across all tests within the same test class. + /// PerClass, + + /// + /// The instance is shared across all tests within the same assembly. + /// PerAssembly, + + /// + /// The instance is shared across all tests in the entire test session. + /// PerTestSession, + + /// + /// The instance is shared across tests that specify the same key. + /// Use the Key or Keys property on the data source attribute to specify the key. + /// Keyed, } diff --git a/TUnit.Core/Attributes/TestHooks/AfterAttribute.cs b/TUnit.Core/Attributes/TestHooks/AfterAttribute.cs index 6d2df6047e..817acf0b17 100644 --- a/TUnit.Core/Attributes/TestHooks/AfterAttribute.cs +++ b/TUnit.Core/Attributes/TestHooks/AfterAttribute.cs @@ -4,5 +4,39 @@ namespace TUnit.Core; +/// +/// Marks a method as a teardown hook that runs after a specific scope (test, class, assembly, or test session). +/// The hook is scoped to the class that declares it. +/// +/// +/// +/// Use [After(HookType.Test)] to run the method after each test in the declaring class. +/// Use [After(HookType.Class)] to run the method once after all tests in the class complete. +/// Use [After(HookType.Assembly)] to run the method once after all tests in the assembly complete. +/// Use [After(HookType.TestSession)] to run the method once per test session teardown. +/// +/// +/// For hooks that apply globally to every test regardless of class, use instead. +/// +/// +/// +/// +/// public class MyTests +/// { +/// [After(HookType.Test)] +/// public void CleanUp() +/// { +/// // Runs after each test in this class +/// } +/// +/// [After(HookType.Class)] +/// public static void ClassCleanUp() +/// { +/// // Runs once after all tests in this class complete +/// } +/// } +/// +/// +/// The scope at which this hook runs. [AttributeUsage(AttributeTargets.Method)] public sealed class AfterAttribute(HookType hookType, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) : HookAttribute(hookType, file, line); diff --git a/TUnit.Core/Attributes/TestHooks/AfterEveryAttribute.cs b/TUnit.Core/Attributes/TestHooks/AfterEveryAttribute.cs index 8ad2189d3e..d0796a9e8d 100644 --- a/TUnit.Core/Attributes/TestHooks/AfterEveryAttribute.cs +++ b/TUnit.Core/Attributes/TestHooks/AfterEveryAttribute.cs @@ -2,6 +2,39 @@ namespace TUnit.Core; +/// +/// Marks a method as a global teardown hook that runs after every test, class, assembly, or test session, +/// regardless of which class declares it. +/// +/// +/// +/// Unlike which is scoped to the declaring class, +/// [AfterEvery] applies globally. For example, [AfterEvery(HookType.Test)] runs after +/// every test in the entire test suite, not just tests in the declaring class. +/// +/// +/// The method must be static and declared in a class. It will be invoked for all tests matching the specified scope. +/// +/// +/// +/// +/// public class GlobalHooks +/// { +/// [AfterEvery(HookType.Test)] +/// public static void AfterEachTest(TestContext context) +/// { +/// // Runs after every test in the entire suite +/// } +/// +/// [AfterEvery(HookType.Class)] +/// public static void AfterEachClass(ClassHookContext context) +/// { +/// // Runs after every test class +/// } +/// } +/// +/// +/// The scope at which this hook runs. #pragma warning disable CS9113 [AttributeUsage(AttributeTargets.Method)] public sealed class AfterEveryAttribute(HookType hookType, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) : HookAttribute(hookType, file, line); diff --git a/TUnit.Core/Attributes/TestHooks/BeforeAttribute.cs b/TUnit.Core/Attributes/TestHooks/BeforeAttribute.cs index 244c39f6ed..a9a9a07ffe 100644 --- a/TUnit.Core/Attributes/TestHooks/BeforeAttribute.cs +++ b/TUnit.Core/Attributes/TestHooks/BeforeAttribute.cs @@ -4,5 +4,39 @@ namespace TUnit.Core; +/// +/// Marks a method as a setup hook that runs before a specific scope (test, class, assembly, or test session). +/// The hook is scoped to the class that declares it. +/// +/// +/// +/// Use [Before(HookType.Test)] to run the method before each test in the declaring class. +/// Use [Before(HookType.Class)] to run the method once before all tests in the class. +/// Use [Before(HookType.Assembly)] to run the method once before all tests in the assembly. +/// Use [Before(HookType.TestSession)] to run the method once per test session. +/// +/// +/// For hooks that apply globally to every test regardless of class, use instead. +/// +/// +/// +/// +/// public class MyTests +/// { +/// [Before(HookType.Test)] +/// public void SetUp() +/// { +/// // Runs before each test in this class +/// } +/// +/// [Before(HookType.Class)] +/// public static void ClassSetUp() +/// { +/// // Runs once before all tests in this class +/// } +/// } +/// +/// +/// The scope at which this hook runs. [AttributeUsage(AttributeTargets.Method)] public sealed class BeforeAttribute(HookType hookType, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) : HookAttribute(hookType, file, line); diff --git a/TUnit.Core/Attributes/TestHooks/BeforeEveryAttribute.cs b/TUnit.Core/Attributes/TestHooks/BeforeEveryAttribute.cs index 584f582141..1d81fe0df7 100644 --- a/TUnit.Core/Attributes/TestHooks/BeforeEveryAttribute.cs +++ b/TUnit.Core/Attributes/TestHooks/BeforeEveryAttribute.cs @@ -2,6 +2,39 @@ namespace TUnit.Core; +/// +/// Marks a method as a global setup hook that runs before every test, class, assembly, or test session, +/// regardless of which class declares it. +/// +/// +/// +/// Unlike which is scoped to the declaring class, +/// [BeforeEvery] applies globally. For example, [BeforeEvery(HookType.Test)] runs before +/// every test in the entire test suite, not just tests in the declaring class. +/// +/// +/// The method must be static and declared in a class. It will be invoked for all tests matching the specified scope. +/// +/// +/// +/// +/// public class GlobalHooks +/// { +/// [BeforeEvery(HookType.Test)] +/// public static void BeforeEachTest(TestContext context) +/// { +/// // Runs before every test in the entire suite +/// } +/// +/// [BeforeEvery(HookType.Class)] +/// public static void BeforeEachClass(ClassHookContext context) +/// { +/// // Runs before every test class +/// } +/// } +/// +/// +/// The scope at which this hook runs. #pragma warning disable CS9113 [AttributeUsage(AttributeTargets.Method)] public sealed class BeforeEveryAttribute(HookType hookType, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) : HookAttribute(hookType, file, line); diff --git a/TUnit.Core/Attributes/TestHooks/HookAttribute.cs b/TUnit.Core/Attributes/TestHooks/HookAttribute.cs index 6e8755511f..7cd2cf598d 100644 --- a/TUnit.Core/Attributes/TestHooks/HookAttribute.cs +++ b/TUnit.Core/Attributes/TestHooks/HookAttribute.cs @@ -1,9 +1,27 @@ namespace TUnit.Core; +/// +/// Base class for hook attributes (, , +/// , ). +/// +/// +/// This class is not intended to be used directly. Use the derived attributes instead. +/// public class HookAttribute : TUnitAttribute { + /// + /// Gets the scope at which this hook runs (Test, Class, Assembly, TestSession, or TestDiscovery). + /// public HookType HookType { get; } + + /// + /// Gets the source file path where this hook is defined. + /// public string File { get; } + + /// + /// Gets the line number in the source file where this hook is defined. + /// public int Line { get; } internal HookAttribute(HookType hookType, string file, int line) @@ -18,5 +36,9 @@ internal HookAttribute(HookType hookType, string file, int line) Line = line; } + /// + /// Gets or sets the execution order of this hook relative to other hooks at the same scope. + /// Lower values execute first. Default is 0. + /// public int Order { get; init; } } diff --git a/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs b/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs index cf6077d71a..08c3fb42bb 100644 --- a/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/ExecutionPriorityAttribute.cs @@ -3,12 +3,41 @@ namespace TUnit.Core; +/// +/// Sets the execution priority for a test, class, or assembly. +/// Higher priority tests are scheduled to execute before lower priority tests. +/// +/// +/// Priority values range from (runs last) to +/// (runs first). The default is . +/// +/// +/// +/// [Test] +/// [ExecutionPriority(Priority.High)] +/// public void ImportantTest() { } +/// +/// [Test] +/// [ExecutionPriority(Priority.Low)] +/// public void LessImportantTest() { } +/// +/// +/// The execution priority level. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)] public class ExecutionPriorityAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute { + /// + /// Gets the execution priority level for the test. + /// public Priority Priority { get; } + + /// public int Order => 0; + /// + /// Initializes a new instance of the class. + /// + /// The execution priority level. Defaults to . public ExecutionPriorityAttribute(Priority priority = Priority.Normal) { Priority = priority; diff --git a/TUnit.Core/Attributes/TestMetadata/ExplicitAttribute.cs b/TUnit.Core/Attributes/TestMetadata/ExplicitAttribute.cs index 22224c060e..afc20b9528 100644 --- a/TUnit.Core/Attributes/TestMetadata/ExplicitAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/ExplicitAttribute.cs @@ -2,11 +2,33 @@ namespace TUnit.Core; +/// +/// Marks a test method or class to only run when explicitly selected. +/// Tests marked with this attribute will not run as part of normal test execution +/// and must be targeted directly by name or filter. +/// +/// +/// This is useful for long-running tests, tests that require specific environments, +/// or tests that should only be run manually by a developer. +/// +/// +/// +/// [Test] +/// [Explicit] +/// public void LongRunningPerformanceTest() +/// { +/// // Only runs when explicitly selected +/// } +/// +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class ExplicitAttribute( [CallerFilePath] string callerFile = "", [CallerMemberName] string callerMemberName = "") : TUnitAttribute { + /// + /// Gets a string identifying where this attribute was applied (file path and member name). + /// public string For { get; } = $"{callerFile} {callerMemberName}".Trim(); } diff --git a/TUnit.Core/HookType.cs b/TUnit.Core/HookType.cs index 14e90f29d3..db3293c6ab 100644 --- a/TUnit.Core/HookType.cs +++ b/TUnit.Core/HookType.cs @@ -1,5 +1,9 @@ namespace TUnit.Core; +/// +/// Specifies the scope at which a hook (, , +/// , ) runs. +/// public enum HookType { /// diff --git a/TUnit.Core/TestContext.cs b/TUnit.Core/TestContext.cs index 37641c0f38..50cd62cadb 100644 --- a/TUnit.Core/TestContext.cs +++ b/TUnit.Core/TestContext.cs @@ -11,8 +11,22 @@ namespace TUnit.Core; /// -/// Simplified test context for the new architecture +/// Provides access to the current test's metadata, execution state, output, and configuration. +/// Use to access the context of the currently executing test. /// +/// +/// +/// TestContext exposes its functionality through organized interface properties: +/// for test lifecycle and result management, +/// for capturing output and attaching artifacts, +/// for test identity and details, +/// for parallel execution control, +/// for test dependency information, +/// for storing custom state during test execution, +/// for subscribing to test lifecycle events, +/// and for creating unique resource names. +/// +/// [DebuggerDisplay("{TestDetails.ClassType.Name}.{GetDisplayName(),nq}")] public partial class TestContext : Context, ITestExecution, ITestParallelization, ITestOutput, ITestMetadata, ITestDependencies, ITestStateBag, ITestEvents, ITestIsolation @@ -35,16 +49,49 @@ public TestContext(string testName, IServiceProvider serviceProvider, ClassHookC _testContextsById[Id] = this; } + /// + /// Gets the unique identifier for this test instance. + /// public string Id { get; } - // Zero-allocation interface properties for organized API access + /// + /// Gets access to test execution state, result management, cancellation, and retry information. + /// public ITestExecution Execution => this; + + /// + /// Gets access to parallel execution control and priority configuration. + /// public ITestParallelization Parallelism => this; + + /// + /// Gets access to test output capture, timing, and artifact management. + /// public ITestOutput Output => this; + + /// + /// Gets access to test identity, details, and display name configuration. + /// public ITestMetadata Metadata => this; + + /// + /// Gets access to test dependency information and relationship queries. + /// public ITestDependencies Dependencies => this; + + /// + /// Gets access to a thread-safe bag for storing custom state during test execution. + /// public ITestStateBag StateBag => this; + + /// + /// Gets access to test lifecycle events (registered, start, end, skip, retry, dispose). + /// public ITestEvents Events => this; + + /// + /// Gets access to helpers for creating isolated resource names unique to this test instance. + /// public ITestIsolation Isolation => this; internal IServiceProvider Services => ServiceProvider; @@ -61,6 +108,20 @@ public TestContext(string testName, IServiceProvider serviceProvider, ClassHookC private string? _buildTimeOutput; private string? _buildTimeErrorOutput; + /// + /// Gets the for the currently executing test, or null if no test is running. + /// This is an async-local property that is automatically set by the test engine. + /// + /// + /// + /// [Test] + /// public void MyTest() + /// { + /// var context = TestContext.Current!; + /// context.Output.WriteLine("Running test: " + context.Metadata.TestName); + /// } + /// + /// public static new TestContext? Current { get => TestContexts.Value; @@ -71,8 +132,16 @@ internal set } } + /// + /// Gets a by its unique identifier, or null if not found. + /// + /// The unique identifier of the test context. + /// The matching , or null. public static TestContext? GetById(string id) => _testContextsById.GetValueOrDefault(id); + /// + /// Gets the dictionary of test parameters indexed by parameter name. + /// public static IReadOnlyDictionary> Parameters => InternalParametersDictionary; private static IConfiguration? _configuration; @@ -91,6 +160,10 @@ public static IConfiguration Configuration internal set => _configuration = value; } + /// + /// Gets the output directory of the test assembly, or null if it cannot be determined. + /// This is typically the directory where the test binaries are located. + /// public static string? OutputDirectory { get @@ -112,6 +185,9 @@ string GetOutputDirectory() } } + /// + /// Gets or sets the current working directory for the test process. + /// public static string WorkingDirectory { get => Environment.CurrentDirectory; @@ -147,7 +223,7 @@ internal void SetDataSourceDisplayName(string displayName) /// - /// Will be null until initialized by TestOrchestrator + /// Gets the class-level hook context for this test, providing access to class-scoped hooks and state. /// public ClassHookContext ClassContext { get; } @@ -176,6 +252,9 @@ internal override void SetAsyncLocalContext() /// internal bool IsDiscoveryInstanceReused { get; set; } + /// + /// Gets a synchronization object that can be used for thread-safe operations within this test context. + /// public object Lock { get; } = new(); diff --git a/TUnit.Core/TestDetails.cs b/TUnit.Core/TestDetails.cs index 4227e5b81c..e8869869e3 100644 --- a/TUnit.Core/TestDetails.cs +++ b/TUnit.Core/TestDetails.cs @@ -5,7 +5,9 @@ namespace TUnit.Core; /// -/// Simplified test details for the new architecture +/// Contains detailed metadata about a test, including its identity, class type, method information, +/// arguments, timeout, retry settings, categories, and custom properties. +/// Access via and its property. /// public partial class TestDetails : ITestIdentity, ITestClass, ITestMethod, ITestConfiguration, ITestLocation, ITestDetailsMetadata { diff --git a/TUnit.Core/TestResult.cs b/TUnit.Core/TestResult.cs index c37fe74e38..0b16ffc349 100644 --- a/TUnit.Core/TestResult.cs +++ b/TUnit.Core/TestResult.cs @@ -2,23 +2,45 @@ namespace TUnit.Core; +/// +/// Represents the outcome of a test execution, including the state, timing, exception information, and output. +/// Access via on . +/// public record TestResult { /// - /// Only final states should be used. + /// Gets the final state of the test (Passed, Failed, Skipped, Timeout, or Cancelled). /// public required TestState State { get; init; } + /// + /// Gets the timestamp when test execution started, or null if the test did not start. + /// public required DateTimeOffset? Start { get; init; } + /// + /// Gets the timestamp when test execution ended, or null if the test did not complete. + /// public required DateTimeOffset? End { get; init; } + /// + /// Gets the duration of test execution, or null if the test did not complete. + /// public required TimeSpan? Duration { get; init; } + /// + /// Gets the exception that caused the test to fail, or null if the test passed or was skipped. + /// public required Exception? Exception { get; init; } + /// + /// Gets the name of the computer where the test was executed. + /// public required string ComputerName { get; init; } + /// + /// Gets the captured standard output from the test execution. + /// public string? Output { get; internal set; } [JsonIgnore] diff --git a/TUnit.Core/TestState.cs b/TUnit.Core/TestState.cs index 54b6604ad0..21dd64e835 100644 --- a/TUnit.Core/TestState.cs +++ b/TUnit.Core/TestState.cs @@ -1,14 +1,52 @@ namespace TUnit.Core; +/// +/// Represents the possible states of a test during its lifecycle. +/// public enum TestState { + /// + /// The test has not yet started execution. + /// NotStarted, + + /// + /// The test is waiting for its dependencies to complete before it can execute. + /// WaitingForDependencies, + + /// + /// The test is queued and waiting for an available execution slot. + /// Queued, + + /// + /// The test is currently executing. + /// Running, + + /// + /// The test completed successfully. + /// Passed, + + /// + /// The test failed due to an assertion failure or unhandled exception. + /// Failed, + + /// + /// The test was skipped and did not execute. + /// Skipped, + + /// + /// The test exceeded its configured timeout duration. + /// Timeout, + + /// + /// The test was cancelled before completion. + /// Cancelled }