Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 13, 2025

Migration plan for xUnit to NUnit 4.4.0:

  • Setup prerequisites (install .NET 8/9/10, unshallow repo, restore workloads)
  • Verify current tests pass with xUnit
  • Update Directory.Packages.props to replace xUnit/FluentAssertions with NUnit packages
  • Update test project .csproj to reference NUnit packages
  • Create AssemblyInfo.Parallel.cs for parallelization configuration
  • Create tests.runsettings file for CI configuration
  • Convert DisposableExtensionsTests.cs from xUnit to NUnit
  • Convert ReactiveExtensionsTests.cs from xUnit to NUnit
  • Build and verify all tests pass with NUnit (10/10 tests passing)
  • Fix NUnit analyzer errors (NUnit4002)
  • Run final code review and security checks

Summary

Successfully migrated all test projects from xUnit + FluentAssertions to NUnit 4.4.0 with NUnit.Analyzers enabled.

Latest Changes:

Fixed NUnit analyzer errors (NUnit4002) by replacing Assert.That(result, Is.EqualTo(0)) with Assert.That(result, Is.Zero) in 4 test methods. This follows NUnit best practices for comparing numeric values to zero.

Verification:

  • ✅ All 10 tests passing
  • ✅ Clean build with no warnings or errors
  • ✅ NUnit.Analyzers enabled and no violations
  • ✅ Tests work with both default settings and runsettings file
Original prompt

This section details on the original issue you should resolve

<issue_title>Migrate xUnit to nUnit</issue_title>
<issue_description># Migrate xUnit + FluentAssertions to NUnit 4.4.0 with Controlled Concurrency

Prereq steps that must be done for you to be able to compile (see copilot-instructions.md in the .github folder for further details)
1, Install .net 8/9/10 via the install script.
2. unshallow the commits
3. dotnet workload restore in the /src folder

Goal: Convert all test projects from xUnit to NUnit 4.4.0, remove FluentAssertions, use Assert.That style, and configure per-class sequential execution with controlled parallelism across classes for ReactiveUI's static-heavy codebase. If using the static classes inside ReactiveUI, only sequential access to ensure the tests run, otherwise parallelise.

1. Update Test Project Packages

Remove

  • xunit
  • xunit.runner.visualstudio
  • FluentAssertions

Add

  • NUnit (4.4.0)
  • NUnit3TestAdapter (latest stable, e.g., 5.2.0)
  • Microsoft.NET.Test.Sdk (latest stable, e.g., 18.0.1 )

Updated .csproj Example:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="NUnit" Version="4.4.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
  </ItemGroup>
</Project>

2. Configure Concurrency for Static Classes

To prevent concurrency issues with ReactiveUI's static classes, run tests sequentially within each test fixture (class) but allow parallelism across fixtures.

2.1 Assembly-Level Concurrency

Add an AssemblyInfo.Parallel.cs file (outside any namespace) in each test project:

using NUnit.Framework;

[assembly: Parallelizable(ParallelScope.Fixtures)]
[assembly: LevelOfParallelism(4)]
  • ParallelScope.Fixtures: Runs test fixtures in parallel, but tests within each fixture sequentially.
  • LevelOfParallelism(4): Caps parallel workers (adjust based on CI resources, e.g., CPU count/2).

2.2 CI Runsettings

Create a tests.runsettings file at the repo root:

<RunSettings>
  <NUnit>
    <NumberOfTestWorkers>4</NumberOfTestWorkers>
  </NUnit>
</RunSettings>

Usage:

dotnet test --settings tests.runsettings

Override for full serialization (if static state is fragile):

dotnet test -- NUnit.NumberOfTestWorkers=1

3. xUnit to NUnit Attribute & API Mappings

xUnit NUnit 4.4.0 Equivalent
[Fact] [Test]
[Theory] + [InlineData(...)] [TestCase(...)]
[Theory] + [MemberData] [TestCaseSource(nameof(Data))]
[Theory] + [ClassData<T>] [TestCaseSource(typeof(T))]
Assert.Equal(a,b) Assert.That(b, Is.EqualTo(a))
Assert.NotEqual(a,b) Assert.That(b, Is.Not.EqualTo(a))
Assert.True(expr) Assert.That(expr, Is.True)
Assert.False(expr) Assert.That(expr, Is.False)
Assert.Null(x) Assert.That(x, Is.Null)
Assert.NotNull(x) Assert.That(x, Is.Not.Null)
Assert.Throws<T>(...) Assert.Throws<T>(...) or Assert.That(..., Throws.TypeOf<T>())
Assert.Collection/Contains Assert.That(coll, Does.Contain(item))
Trait("Category","X") [Category("X")]
IClassFixture<T> [OneTimeSetUp]/[OneTimeTearDown] with shared state
[Collection("name")] [NonParallelizable] on conflicting fixtures

NOTE: for multiple asserts use the IDisposable Assert.EnterMultipleScope method.

  using (Assert.EnterMultipleScope())
    {
        Assert.That(result.RealPart, Is.EqualTo(5.2));
        Assert.That(result.ImaginaryPart, Is.EqualTo(3.9));
    }

4. Remove FluentAssertions: Convert to Assert.That

Common Conversions:

FluentAssertions NUnit 4.4.0 Equivalent
actual.Should().Be(expected) Assert.That(actual, Is.EqualTo(expected))
actual.Should().NotBe(expected) Assert.That(actual, Is.Not.EqualTo(expected))
flag.Should().BeTrue() Assert.That(flag, Is.True)
value.Should().BeNull() Assert.That(value, Is.Null)
str.Should().Contain("x") Assert.That(str, Does.Contain("x"))
str.Should().StartWith("pre") Assert.That(str, Does.StartWith("pre"))
n.Should().BeGreaterThan(5) Assert.That(n, Is.GreaterThan(5))
n.Should().BeInRange(1,10) Assert.That(n, Is.InRange(1,10))
items.Should().BeEmpty() Assert.That(items, Is.Empty)
items.Should().BeEquivalentTo(new[] {1,2,3}) Assert.That(items, Is.EquivalentTo(new[] {1,2,3}))
act.Should().Throw<InvalidOperationException>() Assert.That(act, Throws.TypeOf<InvalidOperationException>())
`await actAsync.Should().ThrowAsync<InvalidOperationExcept...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@glennawatson
Copy link
Contributor

@copilot You're going to have to re-install all the .NET sdks, since it has a different path, so install 8/9/10

Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot You're going to have to re-install all the .NET sdks, since it has a different path, so install 8/9/10

Re-installed .NET 8, 9, and 10 SDKs to the system location (/usr/share/dotnet). All SDKs are now available, repository is unshallowed, workloads restored, and tests pass successfully.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • aka.ms
    • Triggering command: curl -I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 REDACTED (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Migrate test projects from xUnit to NUnit 4.4.0 Migrate test projects from xUnit to NUnit 4.4.0 Nov 13, 2025
@glennawatson glennawatson marked this pull request as ready for review November 13, 2025 01:02
@glennawatson
Copy link
Contributor

@copilot Get the latest commit, do the normal prereq (install .NET 8/9/10 regardless if any is installed yet since its required due to the environment path changes, unshallow the commits, and dotnet workload restore). I've enabled nunit analyzers and there's a bunch of the tests failing now due to the analyzers. Determine the build errors and fix.

Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot Get the latest commit, do the normal prereq (install .NET 8/9/10 regardless if any is installed yet since its required due to the environment path changes, unshallow the commits, and dotnet w...

Completed all prerequisites and fixed NUnit analyzer errors:

  1. ✅ Installed .NET 8, 9, and 10 SDKs to system location
  2. ✅ Unshallowed repository
  3. ✅ Restored workloads
  4. ✅ Fixed 4 NUnit analyzer errors (NUnit4002) by replacing Is.EqualTo(0) with Is.Zero

All tests passing, build succeeds with no warnings or errors. Commit: 2316e93

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • aka.ms
    • Triggering command: curl -I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 REDACTED (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@codecov
Copy link

codecov bot commented Nov 13, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@glennawatson glennawatson merged commit c7570a6 into main Nov 13, 2025
10 checks passed
@glennawatson glennawatson deleted the copilot/migrate-xunit-to-nunit branch November 13, 2025 01:15
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate xUnit to nUnit

2 participants