Migration Guidance
Compatibility
Ensure you use a compatible runner for NUnit Version 4.
Runner | Minimum Version | Comments |
---|---|---|
NUnit3TestAdapter | 4.5.0 | Used by Visual Studio and dotnet test |
NUnit.Console | 3.15.5 | Note: The 3.16.X series don't work with NUnit version 4 |
Rider/Resharper | 2023.3 | Dec 5, 2023, is in EAP, due to release RSN |
3.x -> 4.x
NUnit 4.0 has a few breaking changes making it neither binary nor source code compatible with NUnit 3.14.0
- Change to Classic Asserts
- Removal of
Assert.That
overloads with format specification andparams
.
Classic Assert migration
There are different ways to migrate these to NUnit 4.0
- Convert Classic Assert to the Constraint model
- Update source code to new namespace and class name
- Using
global using
aliases- In own source file
- In project file or
Directory.Build.props
In the sections below we use the following simple test as an example:
public class Tests
{
[Test]
public void TestSomeCalculation()
{
int actual = SomeCalculation();
Assert.AreEqual(42, actual, "Expected {0} to be 42", actual);
StringAssert.StartsWith("42", actualText, "Expected '{0}' to start with '42'", actualText);
}
private static int SomeCalculation() => 42;
}
Convert Classic Assert to Constraint Model
Although the code can be converted manually, that is a lot of work.
Luckily, the NUnit.Analyzer has had rules and associated code fixes
for a while now. Version 3.10.0 knows about the 2nd non-backward compatible change and will convert the format
specification and params
into a FormattableString
.
Note
Caveat: The analyzers only run when the code compiles, so execute and act on the analyzer before
upgrading nunit
to version 4.0.0
!
In our example code, the analyzer will flag the Assert.AreEqual
as shown below:
Running the associated Transform to constraint model
code fix in Visual Studio will convert the code into:
public class Tests
{
[Test]
public void TestSomeCalculation()
{
int actual = SomeCalculation();
Assert.That(actual, Is.EqualTo(42), $"Expected {actual} to be 42");
}
private static int SomeCalculation() => 42;
}
The analyzer code fix supports Batch Fixing:
This allows changing all corresponding Assert
usages for a document, project or a complete solution.
There are many classic asserts and most of these come with a separate code analyzer rule and code fix. Although it allows full configuration to what classic asserts to keep or convert, it means that a developer has to repeat this process multiple times, once for each assert method.
NUnit.Analyzer also has code fixers for CollectionAssert
and StringAssert
.
string actualText = actual.ToString();
StringAssert.StartsWith("42", actualText, "Expected '{0}' to start with '42'", actualText);
Will be converted into:
string actualText = actual.ToString();
Assert.That(actualText, Does.StartWith("42"), $"Expected '{actualText}' to start with '42'");
There are no code fixers for FileAssert
and DirectoryAssert
. They could be added, but we don't expect these to be
used too much.
Updating from Classic Asserts in NUnit 4.x
If you want to keep the Classic Asserts and not convert them to the constraint model -- but do want to use the new NUnit 4.x naming -- you'll need to update the code manually.
The NUnit.Analyzer can't help here as the code either doesn't compile before the change or after, depending on what
version of nunit
you are compiling with.
If you only use classic asserts, you can get away with a couple of global substitutes:
Convert
using NUnit.Framework;
into bothusing NUnit.Framework;using NUnit.Framework.Legacy;
Depending on your editor you can automatically insert a newline between the two
using
statements.Convert
Assert
intoClassicAssert
.This global substitute will also convert those asserts that have not changed. You can narrow the scope of this substitute to do only the asserts that need converting, but there are quite a lot.
- Convert
Assert.AreEqual
intoClassicAssert.AreEqual
. - Convert
Assert.True
intoClassicAssert.True
. - Similar for
IsTrue
,False
,IsFalse
,Greater
,Less
, ...
Depending on what is less work, alternatively you can reverse the substitution of those that shouldn't have been changed:
- Convert
ClassicAssert.That
intoAssert.That
. - Convert
ClassicAssert.Fail
intoAssert.Fail
. - Convert
ClassicAssert.Throws
intoAssert.Throws
. - Etc.
Or if you use Visual Studio, it will raise an IDE0002 with a code fix that can convert all of those that are not considered classic back to assert in one swoop:
- Convert
Use global using
aliases
If you use SDK 6.0 or newer that supports C#10, you can upgrade without modifying any actual tests by adding the
following aliases to GlobalUsings.cs
global using Assert = NUnit.Framework.Legacy.ClassicAssert;
global using CollectionAssert = NUnit.Framework.Legacy.CollectionAssert;
global using StringAssert = NUnit.Framework.Legacy.StringAssert;
global using DirectoryAssert = NUnit.Framework.Legacy.DirectoryAssert;
global using FileAssert = NUnit.Framework.Legacy.FileAssert;
Note that this doesn't mean you have to target .NET 6.0. This also works if targeting .NET Framework as it is purely done on the source code level.
If you have to do this for multiple projects in a repository, this is not handy.
It seems somebody else thought of that and
it is now possible to add global usings in a
Directory.Build.props
file which will be used by all projects in a repository:
<ItemGroup>
<Using Include="NUnit.Framework.Legacy.ClassicAssert" Alias="Assert" />
<Using Include="NUnit.Framework.Legacy.CollectionAssert" Alias="CollectionAssert" />
<Using Include="NUnit.Framework.Legacy.StringAssert" Alias="StringAssert" />
<Using Include="NUnit.Framework.Legacy.DirectoryAssert" Alias="DirectoryAssert" />
<Using Include="NUnit.Framework.Legacy.FileAssert" Alias="FileAssert" />
</ItemGroup>
The build process now automatically creates a ${Project}.GlobalUsings.g.cs
file
for each project with a contents similar to the one shown above.
Assert.That with format specification and params
overload conversion
These overloads were removed to allow for better messages in case of failure. See The "Towards NUnit 4" article for more information.
NUnit 4.x has been optimized such that these formattable strings only get formatted in case the test is failing.
int actual = SomeCalculation();
Assert.That(actual, Is.EqualTo(42), "Expected {0} to be 42", actual);
Needs to be converted into:
int actual = SomeCalculation();
Assert.That(actual, Is.EqualTo(42), $"Expected {actual} to be 42");
To make this transition easier, the Nunit.Analyzer has been updated with a new rule and corresponding code-fix:
Using NUnit Extension libraries
If your code doesn't call nunit
asserts directly but uses a local NUnitExtension
library or a 3rd party one then
that dependency needs to be upgraded before you can upgrade your own code.
If the library is not NUnit 4.0 compliant, you will get error messages like:
System.MissingMethodException : Method not found: 'Void NUnit.Framework.Assert.That(!!0, NUnit.Framework.Constraints.IResolveConstraint, System.String, System.Object[])'.