Equality
editCompare values
long total = 42;
Assert.Equal(42, total); // numbers of different types are compared by value
var greeting = "Hello";
Assert.Equal("hello", greeting, ignoreCase: true);
var text = "first line\r\nsecond line";
Assert.Equal("first line\nsecond line", text, ignoreLineEndingDifferences: true);
Assert.NotEqual("Goodbye", greeting);- Numbers are compared by value, whatever their types:
42equals42L,42uand42m. - Other values are compared with the equality of their type (
Equals). - Strings are compared ordinally.
ignoreCase: trueignores the case, andignoreLineEndingDifferences: truetreats\r\n,\rand\nas the same line ending, so a text read from a file checked out on Windows matches a literal written with\n. - NotEqual accepts the same arguments and options as Equal, and fails exactly when Equal would pass.
To choose how two values are compared, pass an IEqualityComparer<T>: Assert.Equal("abc", code, StringComparer.OrdinalIgnoreCase).
Compare ref structs
Equal and NotEqual compare two values of a ref struct type that implements IEquatable<T>:
internal readonly ref struct Temperature(int degrees) : IEquatable<Temperature>, IComparable<Temperature>
{
public int Degrees { get; } = degrees;
public bool Equals(Temperature other) => Degrees == other.Degrees;
public int CompareTo(Temperature other) => Degrees.CompareTo(other.Degrees);
}var expected = new Temperature(20);
var actual = new Temperature(20);
Assert.Equal(expected, actual); // compared with IEquatable<Temperature>.Equals
Assert.NotEqual(new Temperature(25), actual);- The values are compared with
IEquatable<T>.Equals. Aref structthat does not implementIEquatable<T>is not accepted. - A
ref structvalue cannot be converted toobject, so the failure message shows the type of the values, not the values. To see a value in the message, assert on one of its members:Assert.Equal(20, temperature.Degrees). Span<T>andReadOnlySpan<T>are compared item by item instead. See Compare collections.- Equal, NotEqual, InRange and NotInRange (see Ranges) are the only assertions that support
ref structvalues for now.
Compare collections
List<int> numbers = [1, 2, 3];
Assert.Equal(new[] { 1, 2, 3 }, numbers); // item by item, whatever the collection types
// Nested collections are compared by content too
var matrix = new List<int[]> { new[] { 1, 2 }, new[] { 3 } };
Assert.Equal(new[] { new[] { 1, 2 }, new[] { 3 } }, matrix);
// Pass a comparer to choose how items are compared
var tags = new[] { "Admin", "User" };
Assert.Equal(new[] { "admin", "user" }, tags, StringComparer.OrdinalIgnoreCase);
// The order matters, unless you use EqualUnordered
Assert.NotEqual(new[] { 3, 2, 1 }, numbers);
Assert.EqualUnordered(new[] { 3, 2, 1 }, numbers);- Collections are compared item by item, in order, whatever their types: an array equals a
List<T>with the same items. Collections nested in items, key/value pairs and tuples are compared by content too. - Two
nullcollections are equal. Multidimensional arrays must also have the same dimensions. - A comparer passed to Equal compares the items.
- The failure message gives the index of the first difference. See Failure messages.
EqualUnordered compares two sequences regardless of the order of their items. Without a comparer, items are compared like Equal compares two values, so nested collections are compared by content and 1 equals 1L. Two null sequences are equal. NotEqualUnordered is its exact complement: it fails when both sequences are null.
Spans and memory (ReadOnlySpan<T>, ReadOnlyMemory<T>) are compared the same way, and so is an IAsyncEnumerable<T>, whose assertions return a task to await. See Collections.
Compare approximately
Floating-point results and timestamps rarely match exactly. Compare them with a tolerance, a number of decimal places or a precision:
var sum = 0.1 + 0.2;
Assert.NotEqual(0.3, sum); // 0.30000000000000004
Assert.Equal(0.3, sum, tolerance: 1e-9); // |expected - actual| <= tolerance
Assert.Equal(3.14, Math.PI, precision: 2); // both values rounded to 2 decimal places
var expected = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc);
var actual = expected.AddMilliseconds(300);
Assert.Equal(expected, actual, TimeSpan.FromSeconds(1));| Overload | Passes when |
|---|---|
Equal(expected, actual, tolerance) | The values are equal, or differ by at most tolerance. For double, float, decimal, Half and other IFloatingPoint<T> types |
Equal(expected, actual, precision) | Both values are equal once rounded to precision decimal places. For double, float and decimal |
Equal(expected, actual, precision, rounding) | The same, with a MidpointRounding mode |
Equal(expected, actual, TimeSpan precision) | The two DateTime or DateTimeOffset values are at most precision apart |
Without a MidpointRounding, values are rounded like Math.Round: to the nearest even digit, so 2.5 rounds to 2. NotEqual has the same overloads.
Compare references
Same passes when both arguments are the same instance, and NotSame when they are not. Use them to check that a method returns the instance it was given, or that a copy is a new object:
var original = new List<int> { 1, 2 };
IReadOnlyList<int> view = original;
var copy = new List<int>(original);
Assert.Same(original, view); // the same instance
Assert.NotSame(original, copy); // another instance...
Assert.Equal(original, copy); // ...with the same contentValue types are boxed on each call, so Same and NotSame are meaningless for them: the analyzer reports it as an error (MFAS0010, MFAS0011).
Compare objects member by member
Equal uses the equality of the type: two instances of a class that does not override Equals are equal only when they are the same instance. To compare their public properties and fields instead, use Equivalent. See Equivalence.