Browse Source
* Headless NUnit: Fix TearDown not always working * Headless NUnit: Handle async SetUp/TearDownbug/focus-within-not-cleared
committed by
GitHub
4 changed files with 245 additions and 48 deletions
@ -0,0 +1,86 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using NUnit.Framework.Interfaces; |
||||
|
using NUnit.Framework.Internal; |
||||
|
using NUnit.Framework.Internal.Commands; |
||||
|
|
||||
|
namespace Avalonia.Headless.NUnit; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 2023-05-10, original comment from Max about NUnit 3:
|
||||
|
/// There are multiple problems with NUnit integration at the moment when we wrote this integration.
|
||||
|
/// NUnit doesn't have extensibility API for running on custom dispatcher/sync-context.
|
||||
|
/// See https://github.com/nunit/nunit/issues/2917 https://github.com/nunit/nunit/issues/2774
|
||||
|
/// To workaround that we had to replace inner TestMethodCommand with our own implementation while keeping original hierarchy of commands.
|
||||
|
/// Which will respect proper async/await awaiting code that works with our session and can be block-awaited to fit in NUnit.
|
||||
|
/// Also, we need to push BeforeTest/AfterTest callbacks to the very same session call.
|
||||
|
/// I hope there will be a better solution without reflection, but for now that's it.
|
||||
|
///
|
||||
|
/// 2026-02-04: the situation hasn't changed at all with NUnit 4.
|
||||
|
/// </summary>
|
||||
|
internal static class NUnitReflectionHelper |
||||
|
{ |
||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionDelegatingTestCommand.InnerCommandFieldName)] |
||||
|
private static extern ref TestCommand DelegatingTestCommand_InnerCommand(DelegatingTestCommand instance); |
||||
|
|
||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionBeforeAndAfterTestCommand.BeforeTestFieldName)] |
||||
|
private static extern ref Action<TestExecutionContext>? BeforeAndAfterTestCommand_BeforeTest(BeforeAndAfterTestCommand instance); |
||||
|
|
||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = ReflectionBeforeAndAfterTestCommand.AfterTestFieldName)] |
||||
|
private static extern ref Action<TestExecutionContext>? BeforeAndAfterTestCommand_AfterTest(BeforeAndAfterTestCommand instance); |
||||
|
|
||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_setUpMethods")] |
||||
|
private static extern ref IList<IMethodInfo> SetUpTearDownItem_SetUpMethods(SetUpTearDownItem instance); |
||||
|
|
||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_tearDownMethods")] |
||||
|
private static extern ref IList<IMethodInfo> SetUpTearDownItem_TearDownMethods(SetUpTearDownItem instance); |
||||
|
|
||||
|
extension(DelegatingTestCommand instance) |
||||
|
{ |
||||
|
public ref TestCommand InnerCommand() |
||||
|
=> ref DelegatingTestCommand_InnerCommand(instance); |
||||
|
} |
||||
|
|
||||
|
extension(BeforeAndAfterTestCommand instance) |
||||
|
{ |
||||
|
public ref Action<TestExecutionContext>? BeforeTest() |
||||
|
=> ref BeforeAndAfterTestCommand_BeforeTest(instance); |
||||
|
|
||||
|
public ref Action<TestExecutionContext>? AfterTest() |
||||
|
=> ref BeforeAndAfterTestCommand_AfterTest(instance); |
||||
|
} |
||||
|
|
||||
|
extension(SetUpTearDownItem instance) |
||||
|
{ |
||||
|
public ref IList<IMethodInfo> SetUpMethods() |
||||
|
=> ref SetUpTearDownItem_SetUpMethods(instance); |
||||
|
|
||||
|
public ref IList<IMethodInfo> TearDownMethods() |
||||
|
=> ref SetUpTearDownItem_TearDownMethods(instance); |
||||
|
} |
||||
|
|
||||
|
private sealed class ReflectionDelegatingTestCommand : DelegatingTestCommand |
||||
|
{ |
||||
|
public ReflectionDelegatingTestCommand(TestCommand innerCommand) |
||||
|
: base(innerCommand) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public const string InnerCommandFieldName = nameof(innerCommand); |
||||
|
|
||||
|
public override TestResult Execute(TestExecutionContext context) |
||||
|
=> throw new NotSupportedException("Reflection-only type, this method should never be called"); |
||||
|
} |
||||
|
|
||||
|
private sealed class ReflectionBeforeAndAfterTestCommand : BeforeAndAfterTestCommand |
||||
|
{ |
||||
|
public ReflectionBeforeAndAfterTestCommand(TestCommand innerCommand) |
||||
|
: base(innerCommand) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public const string BeforeTestFieldName = nameof(BeforeTest); |
||||
|
public const string AfterTestFieldName = nameof(AfterTest); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
#if NUNIT
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Headless.UnitTests; |
||||
|
|
||||
|
public class AsyncSetupTests |
||||
|
{ |
||||
|
private static int s_instanceCount; |
||||
|
|
||||
|
[SetUp] |
||||
|
public async Task SetUp() |
||||
|
{ |
||||
|
await Task.Delay(100); |
||||
|
++s_instanceCount; |
||||
|
} |
||||
|
|
||||
|
[AvaloniaTest, TestCase(1), TestCase(2)] |
||||
|
public void Async_Setup_TearDown_Should_Work(int index) |
||||
|
{ |
||||
|
AssertHelper.Equal(1, s_instanceCount); |
||||
|
} |
||||
|
|
||||
|
[TearDown] |
||||
|
public async Task TearDown() |
||||
|
{ |
||||
|
await Task.Delay(100); |
||||
|
--s_instanceCount; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Headless.UnitTests; |
||||
|
|
||||
|
public class SetupTests |
||||
|
#if XUNIT
|
||||
|
: IDisposable |
||||
|
#endif
|
||||
|
{ |
||||
|
private static int s_instanceCount; |
||||
|
|
||||
|
#if NUNIT
|
||||
|
[SetUp] |
||||
|
public void SetUp() |
||||
|
#elif XUNIT
|
||||
|
public SetupTests() |
||||
|
#endif
|
||||
|
{ |
||||
|
++s_instanceCount; |
||||
|
} |
||||
|
|
||||
|
#if NUNIT
|
||||
|
[AvaloniaTest, TestCase(1), TestCase(2)] |
||||
|
#elif XUNIT
|
||||
|
[AvaloniaTheory, InlineData(1), InlineData(2)] |
||||
|
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage( |
||||
|
"Usage", |
||||
|
"xUnit1026:Theory methods should use all of their parameters", |
||||
|
Justification = "Used to run the test several times")] |
||||
|
#endif
|
||||
|
public void Setup_TearDown_Should_Work(int index) |
||||
|
{ |
||||
|
AssertHelper.Equal(1, s_instanceCount); |
||||
|
} |
||||
|
|
||||
|
#if NUNIT
|
||||
|
[TearDown] |
||||
|
public void TearDown() |
||||
|
#elif XUNIT
|
||||
|
public void Dispose() |
||||
|
#endif
|
||||
|
{ |
||||
|
--s_instanceCount; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue