diff --git a/TUnit.Core/Attributes/ClassConstructorAttribute.cs b/TUnit.Core/Attributes/ClassConstructorAttribute.cs index e2320b674b..d9b197e555 100644 --- a/TUnit.Core/Attributes/ClassConstructorAttribute.cs +++ b/TUnit.Core/Attributes/ClassConstructorAttribute.cs @@ -1,20 +1,37 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using TUnit.Core.Interfaces; namespace TUnit.Core; -public abstract class ClassConstructorAttribute : TUnitAttribute, IDataAttribute +// Base abstract class +public abstract class BaseClassConstructorAttribute : TUnitAttribute, IDataAttribute { - public abstract Type ClassConstructorType { get; } + public abstract Type ClassConstructorType { get; set; } - internal ClassConstructorAttribute() + private protected BaseClassConstructorAttribute() { } + private protected BaseClassConstructorAttribute(Type classType) { ClassConstructorType = classType; } +} + +// Single sealed attribute with both generic and non-generic constructors +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)] +public class ClassConstructorAttribute : BaseClassConstructorAttribute +{ + public ClassConstructorAttribute(Type classConstructorType) + : base(classConstructorType) { + ClassConstructorType = classConstructorType; } + + public override Type ClassConstructorType { get; set; } } +// Generic version for C# [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)] -public sealed class ClassConstructorAttribute<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> - : ClassConstructorAttribute where T : IClassConstructor, new() +public sealed class ClassConstructorAttribute< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() + : ClassConstructorAttribute(typeof(T)) + where T : IClassConstructor, new() { - public override Type ClassConstructorType { get; } = typeof(T); -} \ No newline at end of file + public override Type ClassConstructorType { get; set; } = typeof(T); +} diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet2_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet2_0.verified.txt index 8b4055e702..6a634fe8fa 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet2_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet2_0.verified.txt @@ -77,6 +77,10 @@ namespace TUnit.Core public static TUnit.Core.AsyncEvent operator +(TUnit.Core.AsyncEvent? e, System.Func callback) { } public static TUnit.Core.AsyncEvent? operator -(TUnit.Core.AsyncEvent? e, System.Func callback) { } } + public abstract class BaseClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + { + public abstract System.Type ClassConstructorType { get; set; } + } [System.AttributeUsage(System.AttributeTargets.Method)] public abstract class BaseTestAttribute : TUnit.Core.TUnitAttribute { @@ -116,16 +120,18 @@ namespace TUnit.Core public int Order { get; } public void OnTestDiscovery(TUnit.Core.DiscoveredTestContext discoveredTestContext) { } } - public abstract class ClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] + public class ClassConstructorAttribute : TUnit.Core.BaseClassConstructorAttribute { - public abstract System.Type ClassConstructorType { get; } + public ClassConstructorAttribute(System.Type classConstructorType) { } + public override System.Type ClassConstructorType { get; set; } } [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] public sealed class ClassConstructorAttribute : TUnit.Core.ClassConstructorAttribute where T : TUnit.Core.Interfaces.IClassConstructor, new () { public ClassConstructorAttribute() { } - public override System.Type ClassConstructorType { get; } + public override System.Type ClassConstructorType { get; set; } } public class ClassConstructorMetadata : System.IEquatable { 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 5d97d826a1..25f05ecf02 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 @@ -77,6 +77,10 @@ namespace TUnit.Core public static TUnit.Core.AsyncEvent operator +(TUnit.Core.AsyncEvent? e, System.Func callback) { } public static TUnit.Core.AsyncEvent? operator -(TUnit.Core.AsyncEvent? e, System.Func callback) { } } + public abstract class BaseClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + { + public abstract System.Type ClassConstructorType { get; set; } + } [System.AttributeUsage(System.AttributeTargets.Method)] public abstract class BaseTestAttribute : TUnit.Core.TUnitAttribute { @@ -116,16 +120,18 @@ namespace TUnit.Core public int Order { get; } public void OnTestDiscovery(TUnit.Core.DiscoveredTestContext discoveredTestContext) { } } - public abstract class ClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] + public class ClassConstructorAttribute : TUnit.Core.BaseClassConstructorAttribute { - public abstract System.Type ClassConstructorType { get; } + public ClassConstructorAttribute(System.Type classConstructorType) { } + public override System.Type ClassConstructorType { get; set; } } [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] public sealed class ClassConstructorAttribute<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] T> : TUnit.Core.ClassConstructorAttribute where T : TUnit.Core.Interfaces.IClassConstructor, new () { public ClassConstructorAttribute() { } - public override System.Type ClassConstructorType { get; } + public override System.Type ClassConstructorType { get; set; } } public class ClassConstructorMetadata : System.IEquatable { 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 b908282c17..80966acc5a 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 @@ -77,6 +77,10 @@ namespace TUnit.Core public static TUnit.Core.AsyncEvent operator +(TUnit.Core.AsyncEvent? e, System.Func callback) { } public static TUnit.Core.AsyncEvent? operator -(TUnit.Core.AsyncEvent? e, System.Func callback) { } } + public abstract class BaseClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + { + public abstract System.Type ClassConstructorType { get; set; } + } [System.AttributeUsage(System.AttributeTargets.Method)] public abstract class BaseTestAttribute : TUnit.Core.TUnitAttribute { @@ -116,16 +120,18 @@ namespace TUnit.Core public int Order { get; } public void OnTestDiscovery(TUnit.Core.DiscoveredTestContext discoveredTestContext) { } } - public abstract class ClassConstructorAttribute : TUnit.Core.TUnitAttribute, TUnit.Core.IDataAttribute + [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] + public class ClassConstructorAttribute : TUnit.Core.BaseClassConstructorAttribute { - public abstract System.Type ClassConstructorType { get; } + public ClassConstructorAttribute(System.Type classConstructorType) { } + public override System.Type ClassConstructorType { get; set; } } [System.AttributeUsage(System.AttributeTargets.Assembly | System.AttributeTargets.Class)] public sealed class ClassConstructorAttribute<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] T> : TUnit.Core.ClassConstructorAttribute where T : TUnit.Core.Interfaces.IClassConstructor, new () { public ClassConstructorAttribute() { } - public override System.Type ClassConstructorType { get; } + public override System.Type ClassConstructorType { get; set; } } public class ClassConstructorMetadata : System.IEquatable { diff --git a/TUnit.TestProject.FSharp/ClassConstructorTest.fs b/TUnit.TestProject.FSharp/ClassConstructorTest.fs new file mode 100644 index 0000000000..9c19922192 --- /dev/null +++ b/TUnit.TestProject.FSharp/ClassConstructorTest.fs @@ -0,0 +1,12 @@ +namespace TUnit.TestProject + +open TUnit.Core + +[)>] +type ClassConstructorTest(dummyReferenceTypeClass: DummyReferenceTypeClass) = + + member _.DummyReferenceTypeClass = dummyReferenceTypeClass + + [] + member _.Test() = () + diff --git a/TUnit.TestProject.FSharp/ClassConstructorWithEnumerableTest.fs b/TUnit.TestProject.FSharp/ClassConstructorWithEnumerableTest.fs new file mode 100644 index 0000000000..82b0e0a9c0 --- /dev/null +++ b/TUnit.TestProject.FSharp/ClassConstructorWithEnumerableTest.fs @@ -0,0 +1,28 @@ +namespace TUnit.TestProject.FSharp + +open System +open Microsoft.Extensions.DependencyInjection +open TUnit +open TUnit.TestProject +open TUnit.Core + +[)>] +[] +type ClassConstructorWithEnumerableTest(services: IServiceProvider) = + let mutable isDisposed = false + + [] + member _.Setup() = + if isDisposed then + raise (ObjectDisposedException(nameof(ClassConstructorWithEnumerableTest))) + + [] + [] + member _.DoSomething(value: int) = + ActivatorUtilities.GetServiceOrCreateInstance(services) |> ignore + + static member GetValues() : seq = seq { yield 1; yield 2; yield 3; yield 4 } + + interface IDisposable with + member _.Dispose() = + isDisposed <- true \ No newline at end of file diff --git a/TUnit.TestProject.FSharp/DependencyInjectionClassConstructor.fs b/TUnit.TestProject.FSharp/DependencyInjectionClassConstructor.fs new file mode 100644 index 0000000000..68e86cd75a --- /dev/null +++ b/TUnit.TestProject.FSharp/DependencyInjectionClassConstructor.fs @@ -0,0 +1,26 @@ +namespace TUnit.TestProject + +open System +open System.Threading.Tasks +open Microsoft.Extensions.DependencyInjection +open TUnit.Core.Interfaces + +type DependencyInjectionClassConstructor() = + let serviceProvider: IServiceProvider = + ServiceCollection() + .AddTransient() + .BuildServiceProvider() + let mutable scope : AsyncServiceScope option = None + + interface IClassConstructor with + member _.Create(typ, _) = + if scope.IsNone then + scope <- Some(serviceProvider.CreateAsyncScope()) + ActivatorUtilities.GetServiceOrCreateInstance(scope.Value.ServiceProvider, typ) + + interface ITestEndEventReceiver with + member _.OnTestEnd(_testContext) = + match scope with + | Some s -> s.DisposeAsync() + | None -> ValueTask() + member _.Order = 0 \ No newline at end of file diff --git a/TUnit.TestProject.FSharp/DummyReferenceTypeClass.fs b/TUnit.TestProject.FSharp/DummyReferenceTypeClass.fs new file mode 100644 index 0000000000..ffe2569fd8 --- /dev/null +++ b/TUnit.TestProject.FSharp/DummyReferenceTypeClass.fs @@ -0,0 +1,4 @@ +namespace TUnit.TestProject + +type DummyReferenceTypeClass() = class end + diff --git a/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj b/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj index 468d3060b2..5fce858d96 100644 --- a/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj +++ b/TUnit.TestProject.FSharp/TUnit.TestProject.FSharp.fsproj @@ -13,12 +13,17 @@ + + + + + diff --git a/TUnit.TestProject.VB.NET/ClassConstructorTest.vb b/TUnit.TestProject.VB.NET/ClassConstructorTest.vb new file mode 100644 index 0000000000..659ed14d8e --- /dev/null +++ b/TUnit.TestProject.VB.NET/ClassConstructorTest.vb @@ -0,0 +1,17 @@ +Imports TUnit.Core + + +Public Class ClassConstructorTest + + Public Sub New(dummyReferenceTypeClass As DummyReferenceTypeClass) + Me.DummyReferenceTypeClass = dummyReferenceTypeClass + End Sub + + Public ReadOnly Property DummyReferenceTypeClass As DummyReferenceTypeClass + + + Public Sub Test() + ' Test logic here + End Sub + +End Class \ No newline at end of file diff --git a/TUnit.TestProject.VB.NET/ClassConstructorWithEnumerableTest.vb b/TUnit.TestProject.VB.NET/ClassConstructorWithEnumerableTest.vb new file mode 100644 index 0000000000..d11279aa81 --- /dev/null +++ b/TUnit.TestProject.VB.NET/ClassConstructorWithEnumerableTest.vb @@ -0,0 +1,38 @@ +Imports System +Imports Microsoft.Extensions.DependencyInjection +Imports TUnit +Imports TUnit.Core + + + +Public NotInheritable Class ClassConstructorWithEnumerableTest + Implements IDisposable + + Private _isDisposed As Boolean + Private ReadOnly _services As IServiceProvider + + Public Sub New(services As IServiceProvider) + Me._services = services + End Sub + + + Public Sub Setup() + If _isDisposed Then + Throw New ObjectDisposedException(NameOf(ClassConstructorWithEnumerableTest)) + End If + End Sub + + + + Public Sub DoSomething(value As Integer) + ActivatorUtilities.GetServiceOrCreateInstance(Of DummyReferenceTypeClass)(_services) + End Sub + + Public Shared Function GetValues() As IEnumerable(Of Integer) + Return New Integer() {1, 2, 3, 4} + End Function + + Public Sub Dispose() Implements IDisposable.Dispose + _isDisposed = True + End Sub +End Class \ No newline at end of file diff --git a/TUnit.TestProject.VB.NET/DependencyInjectionClassConstructor.vb b/TUnit.TestProject.VB.NET/DependencyInjectionClassConstructor.vb new file mode 100644 index 0000000000..e1dd3a5516 --- /dev/null +++ b/TUnit.TestProject.VB.NET/DependencyInjectionClassConstructor.vb @@ -0,0 +1,36 @@ +Imports System +Imports System.Threading.Tasks +Imports Microsoft.Extensions.DependencyInjection +Imports TUnit.Core +Imports TUnit.Core.Interfaces + +Public Class DependencyInjectionClassConstructor + Implements IClassConstructor + Implements ITestEndEventReceiver + + Private ReadOnly _serviceProvider As IServiceProvider = CreateServiceProvider() + Private _scope As AsyncServiceScope? = Nothing + + Public Function Create(type As Type, classConstructorMetadata As ClassConstructorMetadata) As Object Implements IClassConstructor.Create + If _scope Is Nothing Then + _scope = _serviceProvider.CreateAsyncScope() + End If + Return ActivatorUtilities.GetServiceOrCreateInstance(_scope.Value.ServiceProvider, type) + End Function + + Public Function OnTestEnd(testContext As AfterTestContext) As ValueTask Implements ITestEndEventReceiver.OnTestEnd + Return _scope.Value.DisposeAsync() + End Function + + Private Shared Function CreateServiceProvider() As IServiceProvider + Return New ServiceCollection(). + AddTransient(Of DummyReferenceTypeClass)(). + BuildServiceProvider() + End Function + + Public ReadOnly Property Order As Integer Implements IEventReceiver.Order + Get + Return 0 + End Get + End Property +End Class \ No newline at end of file diff --git a/TUnit.TestProject.VB.NET/DummyReferenceTypeClass.vb b/TUnit.TestProject.VB.NET/DummyReferenceTypeClass.vb new file mode 100644 index 0000000000..2bb047badc --- /dev/null +++ b/TUnit.TestProject.VB.NET/DummyReferenceTypeClass.vb @@ -0,0 +1,3 @@ +Public Class DummyReferenceTypeClass + +End Class diff --git a/TUnit.TestProject.VB.NET/TUnit.TestProject.VB.NET.vbproj b/TUnit.TestProject.VB.NET/TUnit.TestProject.VB.NET.vbproj index 90dd07f58b..7a6918c49a 100644 --- a/TUnit.TestProject.VB.NET/TUnit.TestProject.VB.NET.vbproj +++ b/TUnit.TestProject.VB.NET/TUnit.TestProject.VB.NET.vbproj @@ -12,6 +12,7 @@ +