Edit

MSTest assertions

Use the Assert classes of the Microsoft.VisualStudio.TestTools.UnitTesting namespace to verify specific functionality. A test method exercises the code in your application but reports correctness only when you include Assert statements.

Overview

MSTest provides three assertion classes:

Class Purpose
Assert General-purpose assertions for values, types, and exceptions.
StringAssert String-specific assertions for patterns, substrings, and comparisons.
CollectionAssert Collection assertions for comparing and validating collections.

Important

For new code, always use the Assert class. The StringAssert and CollectionAssert classes are likely to be deprecated in a future release. They're maintained primarily for backward compatibility, but they're not recommended because splitting assertions across three types hurts discoverability.

All assertion methods accept an optional message parameter that displays when the assertion fails, helping you identify the cause:

Assert.AreEqual(expected, actual, "Values should match after processing");

The Assert class

Use the Assert class to verify that the code under test behaves as expected.

Common assertion methods

[TestMethod]
public async Task AssertExamples()
{
    // Equality
    Assert.AreEqual(5, calculator.Add(2, 3));
    Assert.AreNotEqual(0, result);

    // Reference equality
    Assert.AreSame(expected, actual);
    Assert.AreNotSame(obj1, obj2);

    // Boolean conditions
    Assert.IsTrue(result > 0);
    Assert.IsFalse(string.IsNullOrEmpty(name));

    // Null checks
    Assert.IsNull(optionalValue);
    Assert.IsNotNull(requiredValue);

    // Type checks
    Assert.IsInstanceOfType<IDisposable>(obj);
    Assert.IsNotInstanceOfType<string>(obj);

    // Exception testing (MSTest v3.8+)
    Assert.ThrowsExactly<ArgumentNullException>(() => service.Process(null!));
    await Assert.ThrowsExactlyAsync<InvalidOperationException>(
        async () => await service.ProcessAsync());
}

Available APIs

New collection and equivalence assertions in MSTest 4.3

Note

The following assertion methods were introduced in MSTest 4.3.0.

  • Assert.AreSequenceEqual / Assert.AreNotSequenceEqual — element-wise sequence comparison. Pass SequenceOrder.InAnyOrder to ignore element order.
  • Assert.AreEquivalent / Assert.AreNotEquivalent — deep structural comparison of two objects or collections.
  • Assert.ContainsAll / Assert.DoesNotContainAll — assert that a collection contains (or doesn't contain) every expected element.
  • Assert.AreAllNotNull — assert that every element of a collection is non-null.
  • Assert.AreAllDistinct — assert that all elements of a collection are distinct.
  • Assert.AreAllOfType — assert that every element of a collection is of an expected type.

When comparing collections, prefer these methods over Assert.AreEqual, which compares references rather than elements.

MSTest 4.3 also adds:

  • Assert.AddValueFormatter to customize how values are rendered in assertion failure messages.
  • Span<T> and Memory<T> overloads for Assert.HasCount.
  • Structured assertion failure messages for Assert.IsTrue, Assert.IsFalse, Assert.IsNull, and Assert.IsNotNull that include the evaluated expression.
  • Interpolated-string message overloads for the async Assert.ThrowsAsync/Assert.ThrowsExactlyAsync methods, and rejection of ValueTask<TResult>-returning delegates that would otherwise not be awaited.

Soft assertions with Assert.Scope()

Important

Assert.Scope() is an experimental API. Using it produces the MSTESTEXP diagnostic, which you suppress (for example, with #pragma warning disable MSTESTEXP or in your project's .editorconfig file) to acknowledge that the shape and behavior of the API can change in future releases.

By default, every assertion throws an AssertFailedException as soon as it fails, which ends the test immediately. Assert.Scope() introduces soft assertions: while a scope is active, assertion failures are collected instead of thrown, so execution continues and you can see every failure in the scope at once. When the scope is disposed, the collected failures are reported together:

[TestMethod]
public void ValidatePerson()
{
    using (Assert.Scope())
    {
        Assert.AreEqual("Jane", person.FirstName); // failure collected, execution continues
        Assert.AreEqual("Doe", person.LastName);   // failure collected, execution continues
        Assert.IsTrue(person.IsActive);            // failure collected, execution continues
    }
    // On Dispose, all collected failures are reported together.
}

When the scope is disposed:

  • If exactly one failure was collected, the original AssertFailedException is thrown.
  • If multiple failures were collected, a single AssertFailedException is thrown that wraps all of them in an AggregateException.

Postconditions aren't enforced inside a scope

Because a failing assertion no longer throws inside a scope, code that runs after it can't rely on the assertion having succeeded. This applies to every postcondition, including nullability and type narrowing:

using (Assert.Scope())
{
    Assert.IsNotNull(item);
    // 'item' might still be null here: the failure was collected, not thrown.
    Assert.AreEqual("expected", item.Value);
    // 'item.Value' might not equal "expected" either.
}

If a failed assertion would lead to a NullReferenceException (or any other exception) on a later line within the scope, that secondary exception is a symptom of the already-collected failure, not a separate bug. The original assertion failure is still reported when the scope is disposed.

Value-returning assertions return null/default on failure inside a scope

Some assertions return a value on success—for example, Throws and ThrowsExactly return the caught exception, and ContainsSingle returns the matched element. When one of these assertions fails inside a scope, the failure is collected and the method returns null/default instead of throwing:

using (Assert.Scope())
{
    // No exception is thrown by the lambda, so the assertion fails. The failure is
    // collected and 'ex' is null. Accessing 'ex' below throws NullReferenceException.
    InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => { });
    _ = ex.Message; // NullReferenceException—don't use the return value in a scope
}

Don't rely on the value returned by a soft assertion inside a scope. If you need the returned value (such as the caught exception), call the assertion outside the scope, or restructure the test so nothing depends on the return value until after the scope is disposed.

Assert.Fail and Assert.Inconclusive always throw

Fail and Inconclusive are never soft. They always throw immediately, even inside a scope, because they express an unconditional test outcome. Use one of them when a condition is critical and the rest of the test can't meaningfully continue without it.

Nested scopes aren't supported

You can't nest Assert.Scope() calls. Only one assertion scope can be active at a time.

The StringAssert class

Use the StringAssert class to compare and examine strings.

Warning

The StringAssert class is likely to be deprecated in a future release. It's maintained for backward compatibility only and isn't recommended for new code. All StringAssert methods have equivalents on the Assert class, which offers better discoverability. To migrate existing usages, see analyzer MSTEST0046.

Available APIs are:

The CollectionAssert class

Use the CollectionAssert class to compare collections of objects, or to verify the state of a collection.

Warning

The CollectionAssert class is likely to be deprecated in a future release. It's maintained primarily for backward compatibility and isn't recommended for new code. When an equivalent method exists on Assert (such as Assert.Contains, Assert.DoesNotContain, or Assert.HasCount), use Assert for better discoverability.

Available APIs are:

Create custom assertions with Assert.That

The built-in assertion methods don't cover every scenario. To extend the assertion infrastructure with your own checks, MSTest exposes the Assert.That singleton property as an extensibility hook. You add custom assertions as C# extension methods on the Assert instance type, and callers invoke them with the familiar Assert.That.MyAssertion(...) syntax.

For better discoverability, organize project-wide assertions in a dedicated static class. Custom assertions reached through Assert.That show up alongside the built-in methods in IntelliSense, so consumers don't have to remember a separate helper type.

Author a custom assertion

Add an extension method that targets the Assert type and throws AssertFailedException when the condition fails:

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

public static class CustomAssertExtensions
{
    public static void IsPrime(this Assert assert, int value)
    {
        if (value < 2 || Enumerable.Range(2, (int)Math.Sqrt(value) - 1).Any(i => value % i == 0))
        {
            throw new AssertFailedException($"Assert.That.IsPrime failed. Value <{value}> is not a prime number.");
        }
    }
}

Use a custom assertion

After you import the namespace that contains your extension methods, call your custom assertion through Assert.That:

[TestMethod]
public void Compute_ReturnsPrime()
{
    int result = _calculator.NextPrime(10);
    Assert.That.IsPrime(result);
}

Extension hooks on StringAssert and CollectionAssert

The StringAssert.That and CollectionAssert.That properties expose the same singleton pattern for backward compatibility. For new custom assertions, always target Assert.That. Otherwise, your helpers inherit the same discoverability problems as the legacy classes, and they'll need migration if StringAssert and CollectionAssert are deprecated.

Assert.That property versus Assert.That(...) method

Note

Don't confuse the Assert.That singleton property—used as an extensibility hook—with the Assert.That(() => condition) method added in MSTest 3.8. The latter accepts a Boolean expression and produces detailed failure messages by analyzing the expression tree (for example, Assert.That(() => order.Total > 0)). The two APIs share a name but serve different purposes.

Best practices

  • Use specific assertions: Prefer AreEqual over IsTrue(a == b) for better failure messages.

  • Include descriptive messages: Help identify failures quickly with clear assertion messages.

  • Test one thing at a time: Each test method should verify a single behavior.

  • Use Throws/ThrowsExactly for exceptions: In MSTest v3.8+, prefer Assert.Throws, Assert.ThrowsExactly, and their async counterparts (ThrowsAsync, ThrowsExactlyAsync) over the ExpectedException attribute.

  • Prefer Assert over StringAssert/CollectionAssert: For better discoverability and consistency, use the Assert class. The StringAssert and CollectionAssert classes are likely to be deprecated in a future release.

  • Extend Assert.That for custom assertions: For consistent discoverability, add custom assertions as extension methods on Assert and invoke them through Assert.That. Don't target StringAssert.That or CollectionAssert.That in new code.

The following analyzers help ensure proper usage of assertions:

  • MSTEST0006 - Avoid ExpectedException attribute, use Assert.Throws methods instead.
  • MSTEST0017 - Assertion arguments should be passed in the correct order.
  • MSTEST0023 - Do not negate boolean assertions.
  • MSTEST0025 - Prefer Assert.Fail over always-false conditions.
  • MSTEST0026 - Assertion arguments should avoid conditional access.
  • MSTEST0032 - Review always-true assert conditions.
  • MSTEST0037 - Use proper assert methods.
  • MSTEST0038 - Avoid Assert.AreSame with value types.
  • MSTEST0039 - Use newer Assert.Throws methods.
  • MSTEST0040 - Avoid using asserts in async void context.
  • MSTEST0046 - Use Assert instead of StringAssert.
  • MSTEST0051 - Assert.Throws should contain a single statement.
  • MSTEST0053 - Avoid Assert format parameters.
  • MSTEST0058 - Avoid asserts in catch blocks.

See also