AssertWithIs 1.9.1

dotnet add package AssertWithIs --version 1.9.1
                    
NuGet\Install-Package AssertWithIs -Version 1.9.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AssertWithIs" Version="1.9.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AssertWithIs" Version="1.9.1" />
                    
Directory.Packages.props
<PackageReference Include="AssertWithIs" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AssertWithIs --version 1.9.1
                    
#r "nuget: AssertWithIs, 1.9.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=AssertWithIs&version=1.9.1
                    
Install as a Cake Addin
#tool nuget:?package=AssertWithIs&version=1.9.1
                    
Install as a Cake Tool

plot

Minimalistic Assertion Extensions for .NET

Simple. Readable. Opinionated.

Is is a lightweight assertion library for .NET that focuses on readable, minimal, and fail-fast test expectations — no assertion clutter, no dependencies, no test framework lock-in.

Table of content

Why use Is?

  • Concise: One word. One assertion.
  • Opinionated: Prioritizes clarity over fluent DSL chaining.
  • Test-framework agnostic: Works with xUnit, NUnit, MSTest, or none at all.
  • Self-contained: No dependencies, no configuration, just drop it in.
  • Versatile: Useful for unit tests, guard clauses or other validation checks within your applications.

All public methods in Is are:

  • Extension methods: Designed to be used fluently (e.g., value.Is(...)).

  • Consistently named: Every method starts with Is, making them easy to discover with IntelliSense.

Get It on NuGet

NuGet .NET

The package is published on NuGet under the name AssertWithIs because shorter IDs like Is or Is.Assertions were already taken or reserved.
Despite the package name, the library itself uses the concise Is namespace and generates a single Is.dll.

Architecture

Is is built with a clear, modular architecture designed for extensibility and maintainability. Its core components manage assertions, failure reporting, and provide hooks for custom behaviors.

plot

Error Messages

Is provides clear and concise error messages designed to help you quickly identify issues. Failure messages highlight important parts, display the source of the error (line number and code), and typically follow the pattern: actual (Type) is not expected (Type).

Messages

  • uses colors to highlight important parts
  • displays the source of the error (line number and code)

plot

plot

Built-in Assertions

Is offers a focused set of built-in assertion categories.

The full public API and its extension methods can be found here

Booleans

((1.0 / 3.0) == 0.33333).IsTrue(); // ❌
((1.0 / 3.0) == 0.33333).IsFalse(); // ✅

Collections

Enumerable.Range(1, 3).IsUnique(); // ✅
Enumerable.Range(1, 3).IsEmpty(); // ❌
Enumerable.Range(1, 3).IsIn(0, 1, 2, 3, 4); // ✅
Enumerable.Range(1, 3).IsEquivalentTo(Enumerable.Range(1, 3).Reverse()); // ✅

Comparisons

(1.0 / 3.0).IsApproximately(0.33333); // ❌
(1.0 / 3.0).IsApproximately(0.33333, 0.01); // ✅

5.IsBetween(2, 5); // ❌
5.IsInRange(2, 5); // ✅
5.IsGreaterThan(5); // ❌
5.IsAtLeast(5); // ✅

TimeSpan.Parse("1:23").IsApproximately(TimeSpan.Parse("1:24"), TimeSpan.FromMinutes(1)); // ✅
TimeSpan.Parse("1:23").IsApproximately(TimeSpan.Parse("1:25"), TimeSpan.FromMinutes(1)); // ❌

Delegates

static int DivideByZero(int value) => value / 0;
Action action = () => _ = DivideByZero(1);
action.IsThrowing<DivideByZeroException>(); // ✅

Action action = () => 5.IsGreaterThan(6);
action.IsNotThrowing<Is.NotException>(); // ❌

byte[] buffer = [];
Action action = () => buffer = new byte[1024 * 1024 * 10]; // 10 MB
action.IsAllocatingAtMost(10_300); // ✅
action.IsAllocatingAtMost(10_200); // ❌

Equality

(0.1 + 0.2).IsExactly(0.3); // ❌
(0.1 + 0.2).Is(0.3); // ✅ (automatically checks Approximately)
2.999999f.Is(3f); // ✅
783.0123.Is(783.0124); // ✅

Enumerable.Range(1, 4).Is(1, 2, 3, 4); // ✅
Enumerable.Range(1, 4).Where(x => x % 2 == 0).Is(2, 4); // ✅
Enumerable.Range(1, 4).Where(x => x % 3 == 0).Is(3); // ✅

Null

List<int>? list = null;
list.IsNull(); // ✅
list.IsDefault(); // ✅
list.IsNotNull(); // ❌

Strings

var groups = "hello world".IsMatching("(.*) (.*)"); // ✅
groups[1].Value.Is("world"); // ❌
groups[2].Value.Is("world"); // ✅

"hello world".IsContaining("hell"); // ✅
"hello world".IsStartingWith("hell"); // ✅

Types

"hello".Is<string>(); // ✅
"hello".Is<int>(); // ❌

Custom Assertions

Is makes it straightforward to create your own custom assertions that integrate seamlessly with the library's features, including AssertionContext support and consistent error message formatting.

You'll primarily use the Check fluent API within your custom extension methods.

Syntax: Check.That(condition).Unless(message).

To ensure proper source code line detection in error messages, mark your custom assertion methods or their containing class with either [IsAssertion] <u>or</u> [IsAssertions] attributes from the Is.Core namespace.

Example with IsAssertion(s) attribute

[IsAssertions] // Mark all methods via class attribute
public static class MyCustomAssertions
{
    [IsAssertion] // Or mark individual methods
    public static bool IsEmail(this string value) => Check
        .That(Regex.Match(value, "^(.*)\@(.*)\.(.*)$").Success)
        .Unless(value, "is no email");

    [IsAssertion]
    public static bool IsDigitsOnly(this string value) => Check
        .That(value.All(char.IsDigit))
        .Unless(value, "contains non-digit characters");
}

Your custom assertions integrate seamlessly with the existing fluent style of the library, providing consistent error reporting and soft assertion capabilities.

Test Framework Integration

Is is designed to be framework-agnostic. It achieves this through the ITestAdapter interface. This acts as a hook, allowing you to plug in custom logic to handle and throw exceptions that are specific to your chosen test framework (e.g., NUnit.Framework.AssertionException, Xunit.Sdk.XunitException).

By default, Is uses a DefaultTestAdapter that throws Is.NotException directly for single failures and AggregateException for multiple failures from AssertionContext.

You can hook your custom test adapter via Configuration.TestAdapter. If you do not want exception to be thrown at all, you can set this ITestAdapter to null.

ITestAdapter example for NUnit

public class NUnitTestAdapter : ITestAdapter
{
    public void ReportFailure(Failure failure) =>
        throw new AssertionException(failure.Message);

    public void ReportFailures(string message, List<Failure> failures)
    {
        var messages = string.Join("\n\n", failures.Select(f => f.Message));

        throw new AssertionException($"{message}\n{messages}");
    }
}

Of course, you can throw any exception type of your choice such as AssertFailedException for MS Test or XunitException for xUnit.

Soft Assertions

Sometimes you want to run multiple assertions in a test and evaluate all failures at once, rather than stopping after the first one. The AssertionContext provides exactly that capability, enabling "soft assertions" or "assert all" behavior.

The AssertionContext acts as a temporary scope. When active, any assertion failures are collected internally instead of immediately reporting. This allows your test code to continue executing and gather all relevant failures.

If any assertion failures remain unhandled (i.e., not manually dequeued using NextFailure() or TakeFailures()) when the AssertionContext is disposed, they are reported in bulk.

AssertionContext with using statement

using Is.Core;

using var ctx = AssertionContext.Begin();

"abc".IsContaining("xyz"); // ❌
42.Is(0); // ❌
true.IsTrue(); // ✅

Failure fail1 = ctx.NextFailure(); // Dequeues first failure
Failure fail2 = ctx.NextFailure(); // Dequeues second failure
// At this point, the context is empty. No AggregateException will be thrown on Dispose.

try
{
    using var context = AssertionContext.Begin();

    "foo".Is("bar"); // ❌
    10.IsSmallerThan(5); // ❌
}
catch (AggregateException ex)
{
    ex.InnerExceptions[0].Is<NotException>(); // ✅
    ex.InnerExceptions[1].Is<NotException>(); // ✅
}

The AssertionContext uses AsyncLocal for full async test compatibility, ensuring that only one context is active per async flow at a time.

AssertionContext with Test Framework Attribute

For test frameworks (e.g. NUnit), you can integrate AssertionContext using a custom attribute to automatically manage the context lifetime for test methods or classes.

using Is.Core;
using System.Reflection;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class AssertionContextAttribute : NUnitAttribute, IWrapTestMethod
{
    public TestCommand Wrap(TestCommand command) =>
        new AssertionContextCommand(command);

    private sealed class AssertionContextCommand(TestCommand innerCommand)
        : DelegatingTestCommand(innerCommand)
    {
        public override TestResult Execute(TestExecutionContext testContext)
        {
            var caller = testContext.CurrentTest.Method?.MethodInfo.Name ?? testContext.CurrentTest.Name;

            // Begin the assertion context for the test method
            using var assertionContext = AssertionContext.Begin(caller);

            // Execute the actual test method logic
            return innerCommand.Execute(testContext);
        }
    }
}

You can then apply this attribute to your NUnit tests:

[Test]
[AssertionContext] // This attribute manages the AssertionContext for the test
public void ContextTest_WithAttribute()
{
    false.IsTrue(); // ❌
    4.Is(5); // ❌

    // Verify expected count and dequeue failures
    AssertionContext.Current?.TakeFailures(2)
        .All(failure => failure.Message.IsContaining("is not")); // ✅
}

holds true for MS Test or xUnit.

Disabling ITestAdapter

If you don't want to throw exceptions at all, you can use the ITestAdapter instance in Configuration.Active.TestAdapter to disable any reporting failures to test runners by setting this instance to null.

FailureReport with IFailureObserver

If you prefer failure summaries over exception, Is comes with a MarkDownObserver, that exports every observed Failure by creating one report that includes all failures.

public interface IFailureObserver
{
    void OnFailure(Failure failure);
}

A failure from an object graph comparison for example looks like this.

plot

Of course, you can even create your own IFailureObserver to redirect any failures to your favourite reporting or logging system.

License

MIT – use freely.

Contributing

Ideas, bug reports, or pull requests are always welcome.

Author

Developed with care by chrismo80

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.9.1 11 6/30/2025
1.9.0 12 6/30/2025
1.8.1 13 6/28/2025
1.8.0 82 6/26/2025
1.7.3 91 6/25/2025
1.7.2 101 6/25/2025
1.7.1 102 6/25/2025
1.7.0 89 6/21/2025
1.6.0 130 6/19/2025
1.5.0 129 6/19/2025
1.4.0 126 6/18/2025
1.3.4 125 6/17/2025
1.3.3 127 6/16/2025
1.3.2 130 6/16/2025
1.3.1 191 6/8/2025
1.3.0 107 6/7/2025
1.2.0 135 6/5/2025
1.1.0 136 6/4/2025
1.0.0 140 6/2/2025