Skip to content

Commit 74758cb

Browse files
Copilotglennawatson
andcommitted
Migrate from xUnit to NUnit 4.4.0 with concurrency configuration
Co-authored-by: glennawatson <[email protected]>
1 parent af4108b commit 74758cb

14 files changed

+201
-79
lines changed

.editorconfig

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,4 +552,116 @@ end_of_line = lf
552552
[*.{cmd, bat}]
553553
end_of_line = crlf
554554

555+
#############################################
556+
# NUnit Analyzers — enable all as errors
557+
#############################################
558+
559+
# Structure Rules (NUnit1001 - )
560+
dotnet_diagnostic.NUnit1001.severity = error # TestCase args must match parameter types
561+
dotnet_diagnostic.NUnit1002.severity = error # TestCaseSource should use nameof
562+
dotnet_diagnostic.NUnit1003.severity = error # TestCase provided too few arguments
563+
dotnet_diagnostic.NUnit1004.severity = error # TestCase provided too many arguments
564+
dotnet_diagnostic.NUnit1005.severity = error # ExpectedResult type must match return type
565+
dotnet_diagnostic.NUnit1006.severity = error # ExpectedResult must not be used on void methods
566+
dotnet_diagnostic.NUnit1007.severity = error # Non-void method but no ExpectedResult provided
567+
dotnet_diagnostic.NUnit1008.severity = error # ParallelScope.Self at assembly level has no effect
568+
dotnet_diagnostic.NUnit1009.severity = error # ParallelScope.Children on non-parameterized test
569+
dotnet_diagnostic.NUnit1010.severity = error # ParallelScope.Fixtures on a test method
570+
dotnet_diagnostic.NUnit1011.severity = error # TestCaseSource member does not exist
571+
dotnet_diagnostic.NUnit1012.severity = error # async test method must have non-void return type
572+
dotnet_diagnostic.NUnit1013.severity = error # async method must use non-generic Task when no result
573+
dotnet_diagnostic.NUnit1014.severity = error # async method must use Task<T> when result expected
574+
dotnet_diagnostic.NUnit1015.severity = error # Source type does not implement I(Async)Enumerable
575+
dotnet_diagnostic.NUnit1016.severity = error # Source type lacks default constructor
576+
dotnet_diagnostic.NUnit1017.severity = error # Specified source is not static
577+
dotnet_diagnostic.NUnit1018.severity = error # TestCaseSource param count mismatch (target method)
578+
dotnet_diagnostic.NUnit1019.severity = error # Source does not return I(Async)Enumerable
579+
dotnet_diagnostic.NUnit1020.severity = error # Parameters provided to field/property source
580+
dotnet_diagnostic.NUnit1021.severity = error # ValueSource should use nameof
581+
dotnet_diagnostic.NUnit1022.severity = error # Specified ValueSource is not static
582+
dotnet_diagnostic.NUnit1023.severity = error # ValueSource cannot supply required parameters
583+
dotnet_diagnostic.NUnit1024.severity = error # ValueSource does not return I(Async)Enumerable
584+
dotnet_diagnostic.NUnit1025.severity = error # ValueSource member does not exist
585+
dotnet_diagnostic.NUnit1026.severity = error # Test or setup/teardown method is not public
586+
dotnet_diagnostic.NUnit1027.severity = error # Test method has parameters but no arguments supplied
587+
dotnet_diagnostic.NUnit1028.severity = error # Non-test method is public
588+
dotnet_diagnostic.NUnit1029.severity = error # TestCaseSource param count mismatch (Test method)
589+
dotnet_diagnostic.NUnit1030.severity = error # TestCaseSource parameter type mismatch (Test method)
590+
dotnet_diagnostic.NUnit1031.severity = error # ValuesAttribute args must match parameter types
591+
dotnet_diagnostic.NUnit1032.severity = error # IDisposable field/property should be disposed in TearDown
592+
dotnet_diagnostic.NUnit1033.severity = error # TestContext.Write methods will be obsolete
593+
dotnet_diagnostic.NUnit1034.severity = error # Base TestFixtures should be abstract
594+
dotnet_diagnostic.NUnit1035.severity = error # Range 'step' parameter cannot be zero
595+
dotnet_diagnostic.NUnit1036.severity = error # Range: from < to when step is positive
596+
dotnet_diagnostic.NUnit1037.severity = error # Range: from > to when step is negative
597+
dotnet_diagnostic.NUnit1038.severity = error # Attribute values' types must match parameter type
598+
599+
# Assertion Rules (NUnit2001 - )
600+
dotnet_diagnostic.NUnit2001.severity = error # Prefer Assert.That(..., Is.False) over ClassicAssert.False
601+
dotnet_diagnostic.NUnit2002.severity = error # Prefer Assert.That(..., Is.False) over ClassicAssert.IsFalse
602+
dotnet_diagnostic.NUnit2003.severity = error # Prefer Assert.That(..., Is.True) over ClassicAssert.IsTrue
603+
dotnet_diagnostic.NUnit2004.severity = error # Prefer Assert.That(..., Is.True) over ClassicAssert.True
604+
dotnet_diagnostic.NUnit2005.severity = error # Prefer Is.EqualTo over AreEqual
605+
dotnet_diagnostic.NUnit2006.severity = error # Prefer Is.Not.EqualTo over AreNotEqual
606+
dotnet_diagnostic.NUnit2007.severity = error # Actual value should not be a constant
607+
dotnet_diagnostic.NUnit2008.severity = error # Incorrect IgnoreCase usage
608+
dotnet_diagnostic.NUnit2009.severity = error # Same value used for actual and expected
609+
dotnet_diagnostic.NUnit2010.severity = error # Use EqualConstraint for better messages
610+
dotnet_diagnostic.NUnit2011.severity = error # Use ContainsConstraint for better messages
611+
dotnet_diagnostic.NUnit2012.severity = error # Use StartsWithConstraint for better messages
612+
dotnet_diagnostic.NUnit2013.severity = error # Use EndsWithConstraint for better messages
613+
dotnet_diagnostic.NUnit2014.severity = error # Use SomeItemsConstraint for better messages
614+
dotnet_diagnostic.NUnit2015.severity = error # Prefer Is.SameAs over AreSame
615+
dotnet_diagnostic.NUnit2016.severity = error # Prefer Is.Null over ClassicAssert.Null
616+
dotnet_diagnostic.NUnit2017.severity = error # Prefer Is.Null over ClassicAssert.IsNull
617+
dotnet_diagnostic.NUnit2018.severity = error # Prefer Is.Not.Null over ClassicAssert.NotNull
618+
dotnet_diagnostic.NUnit2019.severity = error # Prefer Is.Not.Null over ClassicAssert.IsNotNull
619+
dotnet_diagnostic.NUnit2020.severity = error # Incompatible types for SameAs constraint
620+
dotnet_diagnostic.NUnit2021.severity = error # Incompatible types for EqualTo constraint
621+
dotnet_diagnostic.NUnit2022.severity = error # Missing property required for constraint
622+
dotnet_diagnostic.NUnit2023.severity = error # Invalid NullConstraint usage
623+
dotnet_diagnostic.NUnit2024.severity = error # Wrong actual type with String constraint
624+
dotnet_diagnostic.NUnit2025.severity = error # Wrong actual type with ContainsConstraint
625+
dotnet_diagnostic.NUnit2026.severity = error # Wrong actual type with SomeItems+EqualConstraint
626+
dotnet_diagnostic.NUnit2027.severity = error # Prefer Is.GreaterThan over ClassicAssert.Greater
627+
dotnet_diagnostic.NUnit2028.severity = error # Prefer Is.GreaterThanOrEqualTo over GreaterOrEqual
628+
dotnet_diagnostic.NUnit2029.severity = error # Prefer Is.LessThan over ClassicAssert.Less
629+
dotnet_diagnostic.NUnit2030.severity = error # Prefer Is.LessThanOrEqualTo over LessOrEqual
630+
dotnet_diagnostic.NUnit2031.severity = error # Prefer Is.Not.SameAs over AreNotSame
631+
dotnet_diagnostic.NUnit2032.severity = error # Prefer Is.Zero over ClassicAssert.Zero
632+
dotnet_diagnostic.NUnit2033.severity = error # Prefer Is.Not.Zero over ClassicAssert.NotZero
633+
dotnet_diagnostic.NUnit2034.severity = error # Prefer Is.NaN over ClassicAssert.IsNaN
634+
dotnet_diagnostic.NUnit2035.severity = error # Prefer Is.Empty over ClassicAssert.IsEmpty
635+
dotnet_diagnostic.NUnit2036.severity = error # Prefer Is.Not.Empty over ClassicAssert.IsNotEmpty
636+
dotnet_diagnostic.NUnit2037.severity = error # Prefer Does.Contain over ClassicAssert.Contains
637+
dotnet_diagnostic.NUnit2038.severity = error # Prefer Is.InstanceOf over ClassicAssert.IsInstanceOf
638+
dotnet_diagnostic.NUnit2039.severity = error # Prefer Is.Not.InstanceOf over ClassicAssert.IsNotInstanceOf
639+
dotnet_diagnostic.NUnit2040.severity = error # Non-reference types for SameAs constraint
640+
dotnet_diagnostic.NUnit2041.severity = error # Incompatible types for comparison constraint
641+
dotnet_diagnostic.NUnit2042.severity = error # Comparison constraint on object
642+
dotnet_diagnostic.NUnit2043.severity = error # Use ComparisonConstraint for better messages
643+
dotnet_diagnostic.NUnit2044.severity = error # Non-delegate actual parameter
644+
dotnet_diagnostic.NUnit2045.severity = error # Use Assert.EnterMultipleScope or Assert.Multiple
645+
dotnet_diagnostic.NUnit2046.severity = error # Use CollectionConstraint for better messages
646+
dotnet_diagnostic.NUnit2047.severity = error # Incompatible types for Within constraint
647+
dotnet_diagnostic.NUnit2048.severity = error # Prefer Assert.That over StringAssert
648+
dotnet_diagnostic.NUnit2049.severity = error # Prefer Assert.That over CollectionAssert
649+
dotnet_diagnostic.NUnit2050.severity = error # NUnit 4 no longer supports string.Format spec
650+
dotnet_diagnostic.NUnit2051.severity = error # Prefer Is.Positive over ClassicAssert.Positive
651+
dotnet_diagnostic.NUnit2052.severity = error # Prefer Is.Negative over ClassicAssert.Negative
652+
dotnet_diagnostic.NUnit2053.severity = error # Prefer Is.AssignableFrom over ClassicAssert.IsAssignableFrom
653+
dotnet_diagnostic.NUnit2054.severity = error # Prefer Is.Not.AssignableFrom over ClassicAssert.IsNotAssignableFrom
654+
dotnet_diagnostic.NUnit2055.severity = error # Prefer Is.InstanceOf<T> over 'is T' expression
655+
dotnet_diagnostic.NUnit2056.severity = error # Prefer Assert.EnterMultipleScope statement over Multiple
656+
657+
# Suppressor Rules (NUnit3001 - )
658+
dotnet_diagnostic.NUnit3001.severity = error # Expression checked in NotNull/IsNotNull/Assert.That
659+
dotnet_diagnostic.NUnit3002.severity = error # Field/Property initialized in SetUp/OneTimeSetUp
660+
dotnet_diagnostic.NUnit3003.severity = error # TestFixture instantiated via reflection
661+
dotnet_diagnostic.NUnit3004.severity = error # Field should be disposed in TearDown/OneTimeTearDown
662+
663+
# Style Rules (NUnit4001 - )
664+
dotnet_diagnostic.NUnit4001.severity = error # Simplify the Values attribute
665+
dotnet_diagnostic.NUnit4002.severity = error # Use Specific constraint
666+
555667
vsspell_dictionary_languages = en-US

src/Directory.Packages.props

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@
3232
<PackageVersion Include="ReactiveUI.Maui" Version="22.1.1" />
3333
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
3434
<PackageVersion Include="System.Formats.Asn1" Version="9.0.0" />
35-
<PackageVersion Include="xunit" Version="2.9.3" />
36-
<PackageVersion Include="xunit.runner.console" Version="2.9.3" />
37-
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
38-
<PackageVersion Include="Xunit.StaFact" Version="3.0.13" />
39-
<PackageVersion Include="FluentAssertions" Version="8.7.1" />
35+
<PackageVersion Include="NUnit" Version="4.4.0" />
36+
<PackageVersion Include="NUnit3TestAdapter" Version="5.2.0" />
37+
<PackageVersion Include="NUnit.Analyzers" Version="4.5.0" />
4038
<PackageVersion Include="Microsoft.Reactive.Testing" Version="6.1.0" />
4139
<PackageVersion Include="PublicApiGenerator" Version="11.5.0" />
4240
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
43-
<PackageVersion Include="Verify.Xunit" Version="31.0.2" />
41+
<PackageVersion Include="Verify.NUnit" Version="31.0.2" />
4442
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0" />
4543
<PackageVersion Include="ReactiveMarbles.SourceGenerator.TestNuGetHelper" Version="1.3.1" />
4644
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.3" />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025 ReactiveUI and contributors. All rights reserved.
2+
// Licensed to the ReactiveUI and contributors under one or more agreements.
3+
// The ReactiveUI and contributors licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using NUnit.Framework;
7+
8+
[assembly: Parallelizable(ParallelScope.Fixtures)]
9+
[assembly: LevelOfParallelism(4)]

src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,22 @@
1616
<PackageReference Include="NuGet.Common" />
1717
<PackageReference Include="NuGet.Protocol" />
1818
<PackageReference Include="System.Formats.Asn1" />
19-
<PackageReference Include="xunit" />
20-
<PackageReference Include="xunit.runner.visualstudio" />
19+
<PackageReference Include="NUnit" />
20+
<PackageReference Include="NUnit3TestAdapter" />
21+
<PackageReference Include="NUnit.Analyzers" />
2122
<PackageReference Include="ReactiveMarbles.SourceGenerator.TestNuGetHelper" />
22-
<PackageReference Include="Verify.Xunit" />
23+
<PackageReference Include="Verify.NUnit" />
2324
<PackageReference Include="Verify.SourceGenerators" />
2425
<PackageReference Include="Basic.Reference.Assemblies.Net80" />
2526
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" VersionOverride="4.12.0" />
2627
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" VersionOverride="4.12.0" />
2728
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" PrivateAssets="all" VersionOverride="4.12.0" />
2829
<PackageReference Include="Microsoft.CodeAnalysis" PrivateAssets="all" VersionOverride="4.12.0" />
2930
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" PrivateAssets="all" VersionOverride="4.12.0" />
30-
<PackageReference Include="FluentAssertions" />
3131
</ItemGroup>
3232

3333
<ItemGroup>
34-
<Using Include="Xunit" />
34+
<Using Include="NUnit.Framework" />
3535
</ItemGroup>
3636

3737
<ItemGroup>

src/ReactiveUI.SourceGenerator.Tests/TestBase.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,36 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using Microsoft.CodeAnalysis;
7-
using Xunit.Abstractions;
87

98
namespace ReactiveUI.SourceGenerator.Tests;
109

1110
/// <summary>
1211
/// A base class for handling test setup and teardown.
1312
/// </summary>
1413
/// <typeparam name="T">Type of Incremental Generator.</typeparam>
15-
/// <seealso cref="Xunit.IAsyncLifetime" />
1614
/// <seealso cref="System.IDisposable" />
17-
/// <param name="testOutputHelper">The output helper.</param>
18-
public abstract class TestBase<T>(ITestOutputHelper testOutputHelper) : IAsyncLifetime, IDisposable
15+
public abstract class TestBase<T> : IDisposable
1916
where T : IIncrementalGenerator, new()
2017
{
2118
/// <summary>
2219
/// Gets the TestHelper instance.
2320
/// </summary>
24-
protected TestHelper<T> TestHelper { get; } = new(testOutputHelper);
21+
protected TestHelper<T> TestHelper { get; } = new();
2522

26-
/// <inheritdoc/>
23+
/// <summary>
24+
/// Initializes the test helper asynchronously.
25+
/// </summary>
26+
/// <returns>A task representing the asynchronous operation.</returns>
27+
[OneTimeSetUp]
2728
public Task InitializeAsync() => TestHelper.InitializeAsync();
2829

29-
/// <inheritdoc/>
30-
public Task DisposeAsync()
30+
/// <summary>
31+
/// Disposes the test helper.
32+
/// </summary>
33+
[OneTimeTearDown]
34+
public void DisposeAsync()
3135
{
3236
TestHelper.Dispose();
33-
return Task.CompletedTask;
3437
}
3538

3639
/// <inheritdoc/>

src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
// The ReactiveUI and contributors licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6-
using FluentAssertions;
7-
86
using Microsoft.CodeAnalysis;
97
using Microsoft.CodeAnalysis.CSharp;
108

@@ -19,7 +17,7 @@
1917
using ReactiveUI.SourceGenerators;
2018
using ReactiveUI.SourceGenerators.WinForms;
2119

22-
using Xunit.Abstractions;
20+
using NUnit.Framework;
2321

2422
namespace ReactiveUI.SourceGenerator.Tests;
2523

@@ -29,8 +27,7 @@ namespace ReactiveUI.SourceGenerator.Tests;
2927
/// </summary>
3028
/// <typeparam name="T">Type of Incremental Generator.</typeparam>
3129
/// <seealso cref="System.IDisposable" />
32-
/// <param name="testOutput">The test output helper for capturing test logs.</param>
33-
public sealed class TestHelper<T>(ITestOutputHelper testOutput) : IDisposable
30+
public sealed class TestHelper<T> : IDisposable
3431
where T : IIncrementalGenerator, new()
3532
{
3633
#pragma warning disable CS0618 // Type or member is obsolete
@@ -127,7 +124,7 @@ public void TestFail(
127124
throw new InvalidOperationException("Must have valid compiler instance.");
128125
}
129126

130-
var utility = new SourceGeneratorUtility(x => testOutput.WriteLine(x));
127+
var utility = new SourceGeneratorUtility(x => TestContext.Out.WriteLine(x));
131128

132129
#pragma warning disable IDE0053 // Use expression body for lambda expression
133130
#pragma warning disable RCS1021 // Convert lambda expression body to expression body
@@ -204,7 +201,7 @@ public SettingsTask RunGeneratorAndCheck(
204201
var prediagnostics = compilation.GetDiagnostics()
205202
.Where(d => d.Severity > DiagnosticSeverity.Warning)
206203
.ToList();
207-
prediagnostics.Should().BeEmpty();
204+
Assert.That(prediagnostics, Is.Empty);
208205
}
209206

210207
var generator = new T();
@@ -224,7 +221,7 @@ public SettingsTask RunGeneratorAndCheck(
224221
{
225222
foreach (var diagnostic in offendingDiagnostics)
226223
{
227-
testOutput.WriteLine($"Diagnostic: {diagnostic.Id} - {diagnostic.GetMessage()}");
224+
TestContext.Out.WriteLine($"Diagnostic: {diagnostic.Id} - {diagnostic.GetMessage()}");
228225
}
229226

230227
throw new InvalidOperationException("Compilation failed due to the above diagnostics.");

src/ReactiveUI.SourceGenerator.Tests/UnitTests/BindableDerivedListGeneratorTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using ReactiveUI.SourceGenerators;
7-
using Xunit.Abstractions;
87

98
namespace ReactiveUI.SourceGenerator.Tests;
109

1110
/// <summary>
1211
/// BindableDerivedListGeneratorTests.
1312
/// </summary>
14-
public class BindableDerivedListGeneratorTests(ITestOutputHelper output) : TestBase<BindableDerivedListGenerator>(output)
13+
[TestFixture]
14+
public class BindableDerivedListGeneratorTests : TestBase<BindableDerivedListGenerator>
1515
{
1616
/// <summary>
1717
/// Tests that the source generator correctly generates reactive properties.
1818
/// </summary>
1919
/// <returns>A task to monitor the async.</returns>
20-
[Fact]
20+
[Test]
2121
public Task FromReactiveProperties()
2222
{
2323
// Arrange: Setup the source code that matches the generator input expectations.

src/ReactiveUI.SourceGenerator.Tests/UnitTests/IViewForGeneratorTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using ReactiveUI.SourceGenerators;
7-
using Xunit.Abstractions;
87

98
namespace ReactiveUI.SourceGenerator.Tests;
109

1110
/// <summary>
1211
/// IViewForGeneratorTests.
1312
/// </summary>
14-
public class IViewForGeneratorTests(ITestOutputHelper output) : TestBase<IViewForGenerator>(output)
13+
[TestFixture]
14+
public class IViewForGeneratorTests : TestBase<IViewForGenerator>
1515
{
1616
/// <summary>
1717
/// Tests that the source generator correctly generates reactive properties.
1818
/// </summary>
1919
/// <returns>A task to monitor the async.</returns>
20-
[Fact]
20+
[Test]
2121
public Task FromIViewFor()
2222
{
2323
// Arrange: Setup the source code that matches the generator input expectations.

0 commit comments

Comments
 (0)