Skip to content

chore(tests): upgrade NUnit 3 to 4.6.1 across all test projects - #239

Draft
ottobolyos wants to merge 4 commits into
TrakHound:masterfrom
ottobolyos:chore/nunit-4-upgrade
Draft

chore(tests): upgrade NUnit 3 to 4.6.1 across all test projects#239
ottobolyos wants to merge 4 commits into
TrakHound:masterfrom
ottobolyos:chore/nunit-4-upgrade

Conversation

@ottobolyos

@ottobolyos ottobolyos commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Upgrade every test project from NUnit 3.14.0 to NUnit 4.6.1 — the current stable line — and normalize the fragmented NUnit / NUnit3TestAdapter versions across the test tree that had drifted onto two different baselines.

Motivation

NUnit 3's lifetime is now maintenance-only; NUnit 4 is the active stable line and receives new features, .NET 8/9 hardening, and framework-level fixes. Four of the nine test csprojs still sat on NUnit 3.13.3 with NUnit3TestAdapter 4.3.1/4.5.0, while the other five had already moved to 3.14.0 + 4.6.0 — a fragmented baseline that risked masking version-specific behavior. Bringing everything onto NUnit 4.6.1 clears the drift, aligns the runner surface (relevant for the upcoming Stryker.NET adoption in a follow-up PR), and gets ahead of NUnit 3's deprecation curve.

Approach

Two commits, in the shape [normalize] then [bump]:

  1. chore(tests): normalize NUnit and NUnit3TestAdapter versions across tests — mechanical version alignment across the four lagging csprojs (MTConnect-Compliance-Tests, MTConnect.NET-AgentModule-MqttRelay-Tests, MTConnect.NET-JSON-Tests, MTConnect.NET-JSON-cppagent-Tests) so all nine sit on the highest currently-in-use pair before the framework bump. Zero source changes; behavior identical.

  2. chore(tests): upgrade NUnit 3.14.0 to 4.6.1 across all test projects — every test csproj moves to NUnit 4.6.1, and two compiler-driven source migrations follow:

    • CS0121 delegate-overload ambiguity (84 sites, 20 files). NUnit 4 added Action / Func<T> overloads alongside the legacy TestDelegate / ActualValueDelegate overloads on Assert.DoesNotThrow, Assert.Throws<T>, Assert.ThrowsAsync<T>, Assert.Multiple, and Assert.That. Lambda arguments are now ambiguous under overload resolution, so each affected site casts the lambda to (Action) to unambiguously bind to the new Action overload. Assert.That sites are cast only when the constraint is a Throws.* form (void-lambda variant); the value-returning Is.* form is not affected in this corpus. Where the (Action) cast introduced a bare Action reference in files that only transitively imported System, using System; was added.

    • CS9202 Assert.AreEqual extension-method dispatch (3 sites, 2 files). NUnit 4 removed the classic static Assert.AreEqual and replaced it with an extension-method shim on Assert exposed by NUnit.Framework.Legacy. The extension-method shim requires C# 14 (feature extensions), which the test projects do not target. The safe migration is ClassicAssert.AreEqual from NUnit.Framework.Legacy — a plain static method that works under every C# language version. Two files (AgentUuidDeterministicDefaultTests.cs, XmlStreamsResponseDocumentTests.cs) switch the callers and add using NUnit.Framework.Legacy;.

NUnit3TestAdapter stays at 4.6.0 — the adapter version tracks the adapter's own release line, not the framework version, and 4.6.0 supports both NUnit 3 and NUnit 4 discovery.

Depends on

(no in-flight PR dependencies — merges cleanly against master.)

Dime review cycle 1

Six-agent sweep (code-review, security-audit, simplification, improvement, documentation-audit, test-coverage-audit) at head 3a8954cd. All MEDIUM+ findings closed atomically or tracked with an issue number.

Atomic fix landed on 5e205e3a — swap the 3 ClassicAssert.AreEqual sites to Assert.That(actual, Is.EqualTo(expected)) and drop both using NUnit.Framework.Legacy; imports:

  • [F-CR-001] MEDIUM (code-review) — same recommendation.
  • [F-IMP-002] MEDIUM (improvement) — same recommendation.
  • [F-SIMP-001] LOW (simplification, AgentUuidDeterministicDefaultTests.cs) — same recommendation.
  • [F-SIMP-002] LOW (simplification, XmlStreamsResponseDocumentTests.cs) — same recommendation.

Four agents converged on the same fix — ClassicAssert.AreEqual is NUnit's own "temporary expedience", constraint-based Assert.That reads uniformly with every other assertion in the diff, and the Legacy import is no longer needed. Full-solution build stays at 0 warnings, 0 errors; the two focused suites (AgentUuidDeterministic*, XmlStreamsResponseDocument*) stay green after the swap.

MEDIUM tracked via new issue #240:

  • [F-IMP-001] MEDIUM (improvement) — adopt Directory.Packages.props (Central Package Management). All 9 test csprojs now duplicate NUnit@4.6.1, NUnit3TestAdapter@4.6.0, Microsoft.NET.Test.Sdk@17.14.1, coverlet.collector@6.0.4 — the first drift will be silent (and chore(tests): upgrade NUnit 3 to 4.6.1 across all test projects #239 itself was born from precisely that pattern). Sequenced before the follow-up Stryker.NET adoption PR.

LOW SKIP-rationale — minimal-diff justifies the mechanical shape; constraint-model migration is legitimate follow-up scope:

  • [F-CR-002] — convert 71 (Action)(() => …) casts on Assert.DoesNotThrow / Throws<T> / That(delegate, Throws.…) to Assert.That(() => …, Throws.Nothing) / Throws.TypeOf<T>().
  • [F-CR-004] — same tradeoff on JsonSampleValueToObservationTests.cs and JsonSampleValueConverterEdgeCaseTests.cs.
  • [F-IMP-003](Action)(TestDelegate) sed sweep. Style call; either reads well.

LOW TRACK — follow-up-scoped, not blocking:

  • [F-CR-003] — replace Assert.Multiple((Action)(() => { … })) blocks with using (Assert.EnterMultipleScope()) { … } (NUnit 4.2+). Cast-free and reads as ambient scope; will be flagged as a nice cleanup in a subsequent quality-pass PR.
  • [F-IMP-004] — benchmark [assembly: Parallelizable(ParallelScope.Fixtures)] per test project; suites that bind to fixed HTTP/MQTT ports need per-project audit before flipping. Land under a dedicated performance chore.

CLOSE — confirmations only, no code change:

  • [F-IMP-005] — Stryker.NET follow-up unblocked (no Assert.Throws weakening in diff).
  • [F-SEC-001, F-SEC-002, F-SEC-003] — no CVE surface introduced; every Assert.Throws diff hunk preserves the lambda body verbatim; XmlReaderSettings note is pre-existing pattern outside PR scope.
  • [F-TEST-001, F-TEST-002, F-TEST-003, F-TEST-004] — behavior parity spot-checked across all 20 files; Is.EquivalentTo sites stay on same-type collections; zero test smells introduced.
  • [F-TEST-005] — Docs-Tests bluefin-flake escape hatch already exists (--filter "Category!=E2E"); RouteCheckHelpersTests.cs is deliberately Category-free and runs in either mode.

Documentation audit — CLEAN. No /// doc regression on the 25 touched .cs files; no stale NUnit 3 references in docs/**, root markdown, .github/workflows/*.yml, or CHANGELOG (none exists); MTConnect.NET-Tests-Agents (fixture-only, no NUnit ref) is not referenced anywhere that would become inconsistent.

Head after cycle 1: 5e205e3a6371feca93b9782e71dd4dce7d61bc79. Integration branch integration/up-to-pr-239 at cfbf89448e221233a157e4dd9098f22995d85fd7.

@ottobolyos
ottobolyos force-pushed the chore/nunit-4-upgrade branch from 5e205e3 to 1623569 Compare August 19, 2026 22:06
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
@ottobolyos
ottobolyos force-pushed the chore/nunit-4-upgrade branch 4 times, most recently from 5d41ebf to 6d923ab Compare August 21, 2026 14:30
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
# Conflicts:
#	tests/MTConnect.NET-Common-Tests/V2_6_V2_7/V2_7DataItemTypeTests.cs
#	tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonConditionsArrayShapeTests.cs
…ests

Prior state fragmented four test csprojs at NUnit 3.13.3 and NUnit3TestAdapter
4.3.1/4.5.0 while the other five sat at NUnit 3.14.0 and NUnit3TestAdapter
4.6.0. Normalizing to the highest currently-in-use versions clears the drift
without changing observable behavior and sets a clean baseline for the
subsequent NUnit 3 -> 4 upgrade commit.

Affected projects:
 * tests/Compliance/MTConnect-Compliance-Tests
 * tests/MTConnect.NET-AgentModule-MqttRelay-Tests
 * tests/MTConnect.NET-JSON-Tests
 * tests/MTConnect.NET-JSON-cppagent-Tests

The remaining five test csprojs already sat at the target versions.
Full-solution build (dotnet build -p:IntegrationCoverage=true): 0 warnings, 0
errors.
NUnit 4 is the current stable line; this commit lifts every test csproj onto
4.6.1 and applies the API-surface migrations the upgrade requires.

## Package bumps
All 9 test csprojs move NUnit 3.14.0 to 4.6.1:
 * tests/Compliance/MTConnect-Compliance-Tests
 * tests/MTConnect.NET-AgentModule-MqttRelay-Tests
 * tests/MTConnect.NET-Common-Tests
 * tests/MTConnect.NET-Docs-Tests
 * tests/MTConnect.NET-HTTP-Tests
 * tests/MTConnect.NET-JSON-Tests
 * tests/MTConnect.NET-JSON-cppagent-Tests
 * tests/MTConnect.NET-SHDR-Tests
 * tests/MTConnect.NET-XML-Tests

NUnit3TestAdapter stays at 4.6.0 (adapter version tracks the adapter's own
release line, not the framework version).

## Compiler-driven source migrations

### CS0121 -- delegate-overload ambiguity (84 sites, 20 files)
NUnit 4 added Action / Func<T> overloads alongside the legacy TestDelegate /
ActualValueDelegate overloads for Assert.DoesNotThrow, Assert.Throws<T>,
Assert.ThrowsAsync<T>, Assert.Multiple, and Assert.That. Lambda arguments are
now ambiguous under overload resolution. Each affected call site casts the
lambda to (Action) so the compiler picks the new Action overload
unambiguously. Assert.That sites are cast only when the constraint is a
Throws.* form (void-lambda variant); the value-returning form (Is.*) is not
affected in this corpus.

Where the cast introduced a bare 'Action' reference in files that only
transitively imported the System namespace via other usings, 'using System;'
was added.

### CS9202 -- Assert.AreEqual extension-method dispatch (3 sites, 2 files)
NUnit 4 removed the classic Assert.AreEqual static method and replaced it
with an extension-method shim on Assert exposed by NUnit.Framework.Legacy.
The extension-method shim requires C# 14 (feature 'extensions'), which the
test projects do not target. The safe migration path is
ClassicAssert.AreEqual from NUnit.Framework.Legacy, which is a plain static
method and works under every C# language version. Two files switch the
callers accordingly and add 'using NUnit.Framework.Legacy;'.

Affected:
 * tests/MTConnect.NET-Common-Tests/Agents/AgentUuidDeterministicDefaultTests.cs (1 site)
 * tests/MTConnect.NET-XML-Tests/XmlStreamsResponseDocumentTests.cs (2 error sites; 3 total AreEqual calls in the same file swept together)

## Verification
Full-solution build (dotnet build -p:IntegrationCoverage=true):
    0 Warning(s)
    0 Error(s)
Ultrareview cycle 1 MEDIUM fix. Four independent agents (code-review
F-CR-001, improvement F-IMP-002, simplification F-SIMP-001/F-SIMP-002)
converged on the same recommendation: the three ClassicAssert.AreEqual
sites left in the two files can move to the constraint-based
Assert.That(actual, Is.EqualTo(expected)) idiom that the rest of the
NUnit 4 upgrade already uses, which drops the only NUnit.Framework.Legacy
imports the previous commit introduced and keeps a single assertion
vocabulary across the test corpus.

The prior commit chose ClassicAssert.AreEqual to sidestep NUnit 4s
Assert.AreEqual extension-method shim (which requires C# 14). That
sidestep is unnecessary: Assert.That has been NUnit s recommended
constraint form since 3.x and has no C# 14 dependency, so it lands
without any Legacy indirection.

Behavior parity for value-type equality (string, int) is identical
between ClassicAssert.AreEqual and Assert.That(x, Is.EqualTo(y)) at
these sites; verified via focused test runs (both filters green).

Sites changed:
 * tests/MTConnect.NET-Common-Tests/Agents/AgentUuidDeterministicDefaultTests.cs
   (1 site, drop using NUnit.Framework.Legacy)
 * tests/MTConnect.NET-XML-Tests/XmlStreamsResponseDocumentTests.cs
   (2 sites, drop using NUnit.Framework.Legacy)

Full-solution build: 0 warnings, 0 errors.
@ottobolyos
ottobolyos force-pushed the chore/nunit-4-upgrade branch from 6d923ab to 8646c4d Compare August 21, 2026 16:06
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…r-239

# Conflicts:
#	tests/MTConnect.NET-Common-Tests/V2_6_V2_7/V2_7DataItemTypeTests.cs
#	tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonConditionsArrayShapeTests.cs
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…it 4

NUnit 4's Assert.DoesNotThrow / Assert.Throws<T> resolve delegate arguments
against multiple overloads (Action, TestDelegate, AsyncTestDelegate). A bare
() => lambda triggers CS0121 overload ambiguity once TrakHound#239 lands the NUnit 4
upgrade. The (Action) cast disambiguates unambiguously in both NUnit 3 and 4,
so the wrap is safe to land on this branch before TrakHound#239 merges.

Fixes cross-PR bug class discovered on integration/up-to-pr-249 build (158
errors, 112 CS0121 across 9 test files on 7 PRs). Per-PR fix — each affected
PR wraps its own new test sites so the class stays clean across the train.
NUnit 4 removed the AsyncTestDelegate overload for Assert.That in favour of
Func<Task> — see 'Use Func<Task> instead of AsyncTestDelegate' obsoletion in
nunit.framework.dll 4.6.1. CA2022ShortReadEdgeCaseTests.cs (arrived on
master via merged TrakHound#219) has two Assert.That(async () => ..., Throws.*) sites
that trigger CS0121 overload ambiguity once NUnit 4 is active. Wrapping the
async lambdas with (Func<Task>) disambiguates unambiguously.

This closes the last of the 9-file Bug A cast-fix pass surfaced on the
prior integration/up-to-pr-249 build (158 errors, 112 CS0121). The other
6 branches carry (Action) casts on sync sites in their own tests.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
…r-239

# Conflicts:
#	tests/MTConnect.NET-Common-Tests/V2_6_V2_7/V2_7DataItemTypeTests.cs
#	tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonConditionsArrayShapeTests.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant