From 8cca17b2aeaca2f081dfeff16e8eb8fbcfb0969f Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 00:10:59 +0200 Subject: [PATCH 1/7] =?UTF-8?q?:recycle:=20support=20for=20DisposeAsync=20?= =?UTF-8?q?incl.=20new=20protected=20OnDisposeManagedResourcesAsync=20?= =?UTF-8?q?=F0=9F=A7=B5=20added=20threadsafety=20to=20Dispose=20:heavy=5Fp?= =?UTF-8?q?lus=5Fsign:=20System.Threading.Tasks.Extensions=20for=20netstan?= =?UTF-8?q?dard2.0=20:heavy=5Fplus=5Fsign:=20xunit.extensibility.core=20du?= =?UTF-8?q?e=20to=20IAsyncLifetime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Codebelt.Extensions.Xunit.csproj | 6 ++- .../GlobalSuppressions.cs | 1 + src/Codebelt.Extensions.Xunit/ITest.cs | 21 +++++++- src/Codebelt.Extensions.Xunit/Test.cs | 52 +++++++++++++++++-- 4 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj index 45e0624..76c3a44 100644 --- a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj +++ b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj @@ -11,9 +11,13 @@ - + + + + + diff --git a/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs b/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs index fbdf1b5..8023644 100644 --- a/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs +++ b/src/Codebelt.Extensions.Xunit/GlobalSuppressions.cs @@ -6,3 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is a base class implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Codebelt.Extensions.Xunit.Test")] +[assembly: SuppressMessage("Major Code Smell", "S3971:\"GC.SuppressFinalize\" should not be called", Justification = "False-Positive due to IAsyncDisposable living side-by-side with IDisposable.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Test.DisposeAsync~System.Threading.Tasks.ValueTask")] diff --git a/src/Codebelt.Extensions.Xunit/ITest.cs b/src/Codebelt.Extensions.Xunit/ITest.cs index 5d09798..7605856 100644 --- a/src/Codebelt.Extensions.Xunit/ITest.cs +++ b/src/Codebelt.Extensions.Xunit/ITest.cs @@ -1,12 +1,31 @@ using System; +#if NETSTANDARD2_0_OR_GREATER +using System.Threading.Tasks; +#endif namespace Codebelt.Extensions.Xunit { +#if NETSTANDARD2_0_OR_GREATER + public partial interface ITest + { + /// + /// Asynchronously releases the resources used by the . + /// + /// A that represents the asynchronous dispose operation. + ValueTask DisposeAsync(); + } +#else + /// + public partial interface ITest : IAsyncDisposable + { + } +#endif + /// /// Represents the members needed for vanilla testing. /// /// - public interface ITest : IDisposable + public partial interface ITest : IDisposable { /// /// Gets the type of caller for this instance. Default is . diff --git a/src/Codebelt.Extensions.Xunit/Test.cs b/src/Codebelt.Extensions.Xunit/Test.cs index e8ce291..6994126 100644 --- a/src/Codebelt.Extensions.Xunit/Test.cs +++ b/src/Codebelt.Extensions.Xunit/Test.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; using Xunit.Abstractions; namespace Codebelt.Extensions.Xunit @@ -9,8 +11,10 @@ namespace Codebelt.Extensions.Xunit /// Represents the base class from which all implementations of unit testing should derive. /// /// - public abstract class Test : ITest + public abstract class Test : ITest, IAsyncLifetime { + private readonly object _lock = new(); + /// /// Provides a way, with wildcard support, to determine if matches . /// @@ -82,6 +86,14 @@ protected virtual void OnDisposeManagedResources() { } + /// + /// Called when this object is being disposed by . + /// + protected virtual ValueTask OnDisposeManagedResourcesAsync() + { + return default; + } + /// /// Called when this object is being disposed by either or and is false. /// @@ -105,12 +117,42 @@ public void Dispose() protected void Dispose(bool disposing) { if (Disposed) { return; } - if (disposing) + lock (_lock) { - OnDisposeManagedResources(); + if (Disposed) { return; } + if (disposing) + { + OnDisposeManagedResources(); + } + OnDisposeUnmanagedResources(); + Disposed = true; } - OnDisposeUnmanagedResources(); - Disposed = true; + } + + /// + /// Asynchronously releases the resources used by the . + /// + /// A that represents the asynchronous dispose operation. + /// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#the-disposeasync-method + public async ValueTask DisposeAsync() + { + await OnDisposeManagedResourcesAsync().ConfigureAwait(false); + Dispose(false); + GC.SuppressFinalize(this); + } + + /// + /// Called immediately after the class has been created, before it is used. + /// + /// A that represents the asynchronous operation. + public virtual Task InitializeAsync() + { + return Task.CompletedTask; + } + + Task IAsyncLifetime.DisposeAsync() + { + return DisposeAsync().AsTask(); } } } From a860320a38e9faa1b951b56b56d4fa65a095c548 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 00:12:55 +0200 Subject: [PATCH 2/7] :recycle: support for DisposeAsync incl. new protected OnDisposeManagedResourcesAsync --- .../Codebelt.Extensions.Xunit.Hosting.csproj | 4 -- .../GlobalSuppressions.cs | 1 + .../HostFixture.cs | 57 ++++++++++++++++++- .../IHostFixture.cs | 27 +++++++-- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj b/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj index 491973a..fd2dd45 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj +++ b/src/Codebelt.Extensions.Xunit.Hosting/Codebelt.Extensions.Xunit.Hosting.csproj @@ -34,10 +34,6 @@ - - - - diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs b/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs index b73414a..34a41d4 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs @@ -6,3 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is an implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Codebelt.Extensions.Xunit.Hosting.HostFixture")] +[assembly: SuppressMessage("Major Code Smell", "S3971:\"GC.SuppressFinalize\" should not be called", Justification = "False-Positive due to IAsyncDisposable living side-by-side with IDisposable.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.HostFixture.DisposeAsync~System.Threading.Tasks.ValueTask")] diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs index af3485a..633ce14 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Xunit; namespace Codebelt.Extensions.Xunit.Hosting { @@ -11,7 +12,7 @@ namespace Codebelt.Extensions.Xunit.Hosting /// Provides a default implementation of the interface. /// /// - public class HostFixture : IDisposable, IHostFixture + public class HostFixture : IHostFixture, IAsyncLifetime { private readonly object _lock = new(); @@ -174,6 +175,34 @@ protected virtual void OnDisposeManagedResources() Host?.Dispose(); } + /// + /// Called when this object is being disposed by . + /// +#if NET8_0_OR_GREATER + protected virtual async ValueTask OnDisposeManagedResourcesAsync() + { + if (ServiceProvider is ServiceProvider sp) + { + await sp.DisposeAsync(); + } + + if (Host is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + else + { + Host?.Dispose(); + } + } +#else + protected virtual ValueTask OnDisposeManagedResourcesAsync() + { + OnDisposeManagedResources(); + return default; + } +#endif + /// /// Called when this object is being disposed by either or and is false. /// @@ -208,5 +237,31 @@ protected void Dispose(bool disposing) Disposed = true; } } + + /// + /// Asynchronously releases the resources used by the . + /// + /// A that represents the asynchronous dispose operation. + /// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#the-disposeasync-method + public async ValueTask DisposeAsync() + { + await OnDisposeManagedResourcesAsync().ConfigureAwait(false); + Dispose(false); + GC.SuppressFinalize(this); + } + + /// + /// Called immediately after the class has been created, before it is used. + /// + /// A that represents the asynchronous operation. + public virtual Task InitializeAsync() + { + return Task.CompletedTask; + } + + Task IAsyncLifetime.DisposeAsync() + { + return DisposeAsync().AsTask(); + } } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index c332b0d..83484a8 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -1,30 +1,45 @@ using System; +#if NETSTANDARD2_0_OR_GREATER +using System.Threading.Tasks; +#endif using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Codebelt.Extensions.Xunit.Hosting { - /// - /// Provides a way to use Microsoft Dependency Injection in unit tests. - /// - /// - public interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest - { #if NETSTANDARD2_0_OR_GREATER + public partial interface IHostFixture + { /// /// Gets or sets the delegate that adds configuration and environment information to a . /// /// The delegate that adds configuration and environment information to a . Action ConfigureCallback { get; set; } + + /// + /// Asynchronously releases the resources used by the . + /// + /// A that represents the asynchronous dispose operation. + ValueTask DisposeAsync(); + } #else + public partial interface IHostFixture : IAsyncDisposable + { /// /// Gets or sets the delegate that adds configuration and environment information to a . /// /// The delegate that adds configuration and environment information to a . Action ConfigureCallback { get; set; } + } #endif + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests. + /// + /// + public partial interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable + { /// /// Gets or sets the delegate that adds services to the container. /// From 9a2dcf0151dbc9dc255eaa14b3953cca8a3d9dfa Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 00:13:15 +0200 Subject: [PATCH 3/7] :white_check_mark: additional unit test --- ...ions.Xunit.Hosting.AspNetCore.Tests.csproj | 4 +- .../Assets/InvalidHostTest.cs | 11 ++++ .../Assets/ValidHostTest.cs | 18 ++++++ ...belt.Extensions.Xunit.Hosting.Tests.csproj | 2 +- .../HostFixtureTest.cs | 58 +++++++++++++++++++ .../Assets/AsyncDisposable.cs | 38 ++++++++++++ .../Assets/WemoryStream.cs | 20 +++++++ .../Codebelt.Extensions.Xunit.Tests.csproj | 1 + .../DisposableTest.cs | 33 +++++++++++ .../TestTest.cs | 20 +++++++ 10 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Tests/Assets/AsyncDisposable.cs create mode 100644 test/Codebelt.Extensions.Xunit.Tests/Assets/WemoryStream.cs diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj index 7f9809d..b7f7f06 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs new file mode 100644 index 0000000..9bc2e92 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs @@ -0,0 +1,11 @@ +using Xunit; + +namespace Codebelt.Extensions.Xunit.Hosting.Assets +{ + public class InvalidHostTest : Test, IClassFixture where T : class, IHostFixture + { + public InvalidHostTest(T hostFixture) + { + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs new file mode 100644 index 0000000..4a5d268 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.Assets +{ + public class ValidHostTest : HostTest + { + public ValidHostTest(HostFixture hostFixture) : base(hostFixture) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj index 6c49c08..2ad30d4 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj @@ -15,7 +15,7 @@ - + diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs new file mode 100644 index 0000000..d139fd2 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.Assets; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + public class HostFixtureTest : Test + { + private readonly HostFixture _hostFixture; + + public HostFixtureTest(ITestOutputHelper output) : base(output) + { + _hostFixture = new HostFixture(); + } + + [Fact] + public void ConfigureHost_ShouldThrowArgumentNullException_WhenHostTestIsNull() + { + Assert.Throws(() => _hostFixture.ConfigureHost(null)); + } + + [Fact] + public void ConfigureHost_ShouldThrowArgumentOutOfRangeException_WhenHostTestIsNotAssignableFromHostTest() + { + var invalidHostTest = new InvalidHostTest(new HostFixture()); + Assert.Throws(() => _hostFixture.ConfigureHost(invalidHostTest)); + } + + [Fact] + public void ConfigureHost_ShouldConfigureHostSuccessfully() + { + var validHostTest = new ValidHostTest(_hostFixture); + + _hostFixture.ConfigureHost(validHostTest); + + Assert.NotNull(_hostFixture.Host); + Assert.NotNull(_hostFixture.ServiceProvider); + Assert.NotNull(_hostFixture.Configuration); + Assert.NotNull(_hostFixture.HostingEnvironment); + } + + [Fact] + public void Dispose_ShouldDisposeResources() + { + _hostFixture.Dispose(); + Assert.True(_hostFixture.Disposed); + } + + [Fact] + public async Task DisposeAsync_ShouldDisposeResourcesAsync() + { + await _hostFixture.DisposeAsync(); + Assert.True(_hostFixture.Disposed); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/AsyncDisposable.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/AsyncDisposable.cs new file mode 100644 index 0000000..6adddb0 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/AsyncDisposable.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Codebelt.Extensions.Xunit.Assets +{ + public class AsyncDisposable : Test + { + IDisposable _disposableResource = new MemoryStream(); +#if NET8_0_OR_GREATER + IAsyncDisposable _asyncDisposableResource = new MemoryStream(); +#else + IAsyncDisposable _asyncDisposableResource = new WemoryStream(); +#endif + + protected override void OnDisposeManagedResources() + { + _disposableResource?.Dispose(); + _disposableResource = null; + DisposableResourceCalled = true; + } + + public bool DisposableResourceCalled { get; private set; } + + protected override async ValueTask OnDisposeManagedResourcesAsync() + { + if (_asyncDisposableResource is not null) + { + await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false); + AsyncDisposableResourceCalled = true; + } + _asyncDisposableResource = null; + OnDisposeManagedResources(); + } + + public bool AsyncDisposableResourceCalled { get; private set; } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Tests/Assets/WemoryStream.cs b/test/Codebelt.Extensions.Xunit.Tests/Assets/WemoryStream.cs new file mode 100644 index 0000000..eb02c50 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Tests/Assets/WemoryStream.cs @@ -0,0 +1,20 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Codebelt.Extensions.Xunit.Assets +{ + public class WemoryStream : MemoryStream, IAsyncDisposable + { + protected virtual async ValueTask DisposeAsyncCore() + { + Dispose(false); + } + + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore(); + GC.SuppressFinalize(this); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj index 9fe983e..6ee075b 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs b/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs index e0646e9..22e2265 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs +++ b/test/Codebelt.Extensions.Xunit.Tests/DisposableTest.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Codebelt.Extensions.Xunit.Assets; using Xunit; using Xunit.Abstractions; @@ -11,7 +12,39 @@ public DisposableTest(ITestOutputHelper output) : base(output) { } +#if NET8_0_OR_GREATER + [Fact] + public async Task AsyncDisposable_VerifyThatAssetIsBeingDisposed() + { + AsyncDisposable ad = new AsyncDisposable(); + await using (ad.ConfigureAwait(false)) + { + Assert.NotNull(ad); + Assert.False(ad.DisposableResourceCalled); + Assert.False(ad.AsyncDisposableResourceCalled); + } + Assert.NotNull(ad); + Assert.True(ad.DisposableResourceCalled); + Assert.True(ad.AsyncDisposableResourceCalled); + } +#else + [Fact] + public async Task AsyncDisposable_VerifyThatAssetIsBeingDisposed() + { + AsyncDisposable ad = new AsyncDisposable(); + + Assert.NotNull(ad); + Assert.False(ad.DisposableResourceCalled); + Assert.False(ad.AsyncDisposableResourceCalled); + + await ad.DisposeAsync().ConfigureAwait(false); + + Assert.NotNull(ad); + Assert.True(ad.DisposableResourceCalled); + Assert.True(ad.AsyncDisposableResourceCalled); + } +#endif [Fact] public void ManagedDisposable_VerifyThatAssetIsBeingDisposed() diff --git a/test/Codebelt.Extensions.Xunit.Tests/TestTest.cs b/test/Codebelt.Extensions.Xunit.Tests/TestTest.cs index 333b708..53edd5c 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/TestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Tests/TestTest.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -9,11 +10,30 @@ public class TestTest : Test { private const string ExpectedStringValue = "AllIsGood"; private bool _onDisposeManagedResourcesCalled; + private bool _initializeAsyncCalled; public TestTest(ITestOutputHelper output) : base(output) { } + public override Task InitializeAsync() + { + _initializeAsyncCalled = true; + return Task.CompletedTask; + } + + protected override ValueTask OnDisposeManagedResourcesAsync() + { + TestOutput.WriteLine($"{nameof(IAsyncLifetime.DisposeAsync)} was called."); + return default; + } + + [Fact] + public void Test_InitializeAsyncCalled_ShouldBeTrue() + { + Assert.True(_initializeAsyncCalled); + } + [Fact] public void Test_ShouldHaveTestOutput() { From 6b17a0134655e1bd27ed54db3d2bcc7f9e38e472 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 01:57:31 +0200 Subject: [PATCH 4/7] :speech_balloon: updated community health pages --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84db6e8..6220636 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ This major release is first and foremost focused on ironing out any wrinkles tha ### Added - StringExtensions class in the Codebelt.Extensions.Xunit namespace with one extension method (TFM netstandard2.0) for the String class: ReplaceLineEndings +- ITest interface in the Codebelt.Extensions.Xunit namespace was extended with one new method: DisposeAsync +- Test class in the Codebelt.Extensions.Xunit namespace was extended with three new methods: InitializeAsync, OnDisposeManagedResourcesAsync and DisposeAsync +- IHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace was extended with two new methods: Dispose and DisposeAsync +- HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace was extended with three new methods: InitializeAsync, OnDisposeManagedResourcesAsync, Dispose and DisposeAsync ### Changed From 45c3ba20ea0abf4700e6829cb35f98c5eaa4908e Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 01:57:41 +0200 Subject: [PATCH 5/7] :package: updated NuGet package definition --- .../Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt | 2 ++ .nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt index 28a10d6..0e9f5af 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -10,6 +10,8 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0   # New Features - EXTENDED HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace to enable ValidateOnBuild and ValidateScopes when TFM is .NET 9 (or greater) and started the Host for consistency with AspNetCoreHostFixture +- EXTENDED IHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace with two new methods: Dispose and DisposeAsync +- EXTENDED HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace with three new methods: InitializeAsync, OnDisposeManagedResourcesAsync, Dispose and DisposeAsync   Version 8.4.1 Availability: .NET 8, .NET 6 and .NET Standard 2.0 diff --git a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt index a98ff08..4ed08f4 100644 --- a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt @@ -5,6 +5,10 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0 - CHANGED Dependencies to latest and greatest with respect to TFMs - REMOVED Support for TFM .NET 6 (LTS)   +# New Features +- EXTENDED ITest interface in the Codebelt.Extensions.Xunit namespace with one new method: DisposeAsync +- EXTENDED Test class in the Codebelt.Extensions.Xunit namespace with three new methods: InitializeAsync, OnDisposeManagedResourcesAsync and DisposeAsync +  Version 8.4.0 Availability: .NET 8, .NET 6 and .NET Standard 2.0   From c4205b58fca771cdd11bd6869d280076955d941b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 02:03:29 +0200 Subject: [PATCH 6/7] :white_check_mark: additional unit test --- .../AspNetCoreHostFixtureTest.cs | 65 +++++++++++++++++++ .../Assets/InvalidHostTest.cs | 11 ++++ .../Assets/ValidHostTest.cs | 22 +++++++ 3 files changed, 98 insertions(+) create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs new file mode 100644 index 0000000..243fa39 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs @@ -0,0 +1,65 @@ +using System; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets; +using Microsoft.AspNetCore.Builder; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + public class AspNetCoreHostFixtureTest : Test + { + public AspNetCoreHostFixtureTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void ConfigureHost_NullHostTest_ThrowsArgumentNullException() + { + // Arrange + var fixture = new AspNetCoreHostFixture(); + + // Act & Assert + Assert.Throws(() => fixture.ConfigureHost(null)); + } + + [Fact] + public void ConfigureHost_InvalidHostTestType_ThrowsArgumentOutOfRangeException() + { + // Arrange + var fixture = new AspNetCoreHostFixture(); + var invalidHostTest = new InvalidHostTest(fixture); + + // Act & Assert + Assert.Throws(() => fixture.ConfigureHost(invalidHostTest)); + } + + [Fact] + public void ConfigureApplicationCallback_SetAndGet_ReturnsCorrectValue() + { + // Arrange + var fixture = new AspNetCoreHostFixture(); + Action callback = app => { }; + + // Act + fixture.ConfigureApplicationCallback = callback; + + // Assert + Assert.Equal(callback, fixture.ConfigureApplicationCallback); + } + + [Fact] + public void ConfigureHost_ValidHostTest_ConfiguresHostCorrectly() + { + // Arrange + var fixture = new AspNetCoreHostFixture(); + var hostTest = new ValidHostTest(fixture); + + // Act + fixture.ConfigureHost(hostTest); + + // Assert + Assert.NotNull(fixture.Host); + Assert.NotNull(fixture.Application); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs new file mode 100644 index 0000000..c2ba881 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs @@ -0,0 +1,11 @@ +using Xunit; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets +{ + public class InvalidHostTest : Test, IClassFixture where T : class, IHostFixture + { + public InvalidHostTest(T hostFixture) + { + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs new file mode 100644 index 0000000..544cc78 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets +{ + public class ValidHostTest : AspNetCoreHostTest + { + public ValidHostTest(AspNetCoreHostFixture hostFixture) : base(hostFixture) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + + } + } +} From 314adb96fb56b133cc30b115db936bb15605977c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sat, 26 Oct 2024 02:43:56 +0200 Subject: [PATCH 7/7] :recycle: refactored after coderabbit suggestions --- .../HostFixture.cs | 2 +- .../IHostFixture.cs | 18 +++++++--------- .../Codebelt.Extensions.Xunit.csproj | 1 + src/Codebelt.Extensions.Xunit/ITest.cs | 21 +------------------ .../Codebelt.Extensions.Xunit.Tests.csproj | 1 - 5 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs index 633ce14..340bfeb 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs @@ -239,7 +239,7 @@ protected void Dispose(bool disposing) } /// - /// Asynchronously releases the resources used by the . + /// Asynchronously releases the resources used by the . /// /// A that represents the asynchronous dispose operation. /// https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#the-disposeasync-method diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 83484a8..515d746 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -1,7 +1,4 @@ using System; -#if NETSTANDARD2_0_OR_GREATER -using System.Threading.Tasks; -#endif using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -16,15 +13,9 @@ public partial interface IHostFixture /// /// The delegate that adds configuration and environment information to a . Action ConfigureCallback { get; set; } - - /// - /// Asynchronously releases the resources used by the . - /// - /// A that represents the asynchronous dispose operation. - ValueTask DisposeAsync(); } #else - public partial interface IHostFixture : IAsyncDisposable + public partial interface IHostFixture { /// /// Gets or sets the delegate that adds configuration and environment information to a . @@ -37,8 +28,13 @@ public partial interface IHostFixture : IAsyncDisposable /// /// Provides a way to use Microsoft Dependency Injection in unit tests. /// + /// + /// + /// + /// /// - public partial interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable + /// + public partial interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable, IAsyncDisposable { /// /// Gets or sets the delegate that adds services to the container. diff --git a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj index 76c3a44..caec1bb 100644 --- a/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj +++ b/src/Codebelt.Extensions.Xunit/Codebelt.Extensions.Xunit.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Codebelt.Extensions.Xunit/ITest.cs b/src/Codebelt.Extensions.Xunit/ITest.cs index 7605856..b08272a 100644 --- a/src/Codebelt.Extensions.Xunit/ITest.cs +++ b/src/Codebelt.Extensions.Xunit/ITest.cs @@ -1,31 +1,12 @@ using System; -#if NETSTANDARD2_0_OR_GREATER -using System.Threading.Tasks; -#endif namespace Codebelt.Extensions.Xunit { -#if NETSTANDARD2_0_OR_GREATER - public partial interface ITest - { - /// - /// Asynchronously releases the resources used by the . - /// - /// A that represents the asynchronous dispose operation. - ValueTask DisposeAsync(); - } -#else - /// - public partial interface ITest : IAsyncDisposable - { - } -#endif - /// /// Represents the members needed for vanilla testing. /// /// - public partial interface ITest : IDisposable + public interface ITest : IDisposable, IAsyncDisposable { /// /// Gets the type of caller for this instance. Default is . diff --git a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj index 6ee075b..9fe983e 100644 --- a/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj +++ b/test/Codebelt.Extensions.Xunit.Tests/Codebelt.Extensions.Xunit.Tests.csproj @@ -6,7 +6,6 @@ -