SharpAssert.Runtime
1.3.0
See the version list below for details.
dotnet add package SharpAssert.Runtime --version 1.3.0
NuGet\Install-Package SharpAssert.Runtime -Version 1.3.0
<PackageReference Include="SharpAssert.Runtime" Version="1.3.0" />
<PackageVersion Include="SharpAssert.Runtime" Version="1.3.0" />
<PackageReference Include="SharpAssert.Runtime" />
paket add SharpAssert.Runtime --version 1.3.0
#r "nuget: SharpAssert.Runtime, 1.3.0"
#:package SharpAssert.Runtime@1.3.0
#addin nuget:?package=SharpAssert.Runtime&version=1.3.0
#tool nuget:?package=SharpAssert.Runtime&version=1.3.0
<p align="center"> <img src="https://raw.githubusercontent.com/yevhen/sharp.assert/refs/heads/main/logo.png" alt="SharpAssert logo"/> </p>
SharpAssert
A pytest inspired assertion library for .NET with no special syntax.
Overview
SharpAssert provides rich assertion diagnostics by automatically transforming your assertion expressions at compile time using MSBuild source rewriting, giving you detailed failure messages with powerful expression analysis.
using static SharpAssert.Sharp;
var items = new[] { 1, 2, 3 };
var target = 4;
Assert(items.Contains(target));
// Assertion failed: items.Contains(target) at MyTest.cs:15
// items: [1, 2, 3]
// target: 4
// Result: false
How It Works
SharpAssert uses MSBuild source rewriting to automatically transform your assertion calls at compile time:
- You write:
Assert(x == y) - MSBuild rewrites:
global::SharpAssert.SharpInternal.Assert(() => x == y, "x == y", "file.cs", 42) - Runtime analysis: Expression tree provides detailed failure diagnostics when assertions fail
Features
- ๐ Detailed Expression Analysis - See exactly why your assertions failed
- ๐ฏ Exception Testing -
Throws<T>andThrowsAsync<T>with detailed exception diagnostics - ๐ค String Diffs - Character-level inline diffs for strings (powered by DiffPlex)
- ๐ String Pattern Matching - Wildcard patterns and occurrence counting
- ๐ Collection Comparison - First mismatch, missing/extra elements detection
- ๐ Collection Ordering - Ascending/descending order validation
- ๐ข Collection Uniqueness - Duplicate detection with key selectors
- ๐ Object Deep Diff - Property-level differences for objects/records (powered by Compare-Net-Objects)
- ๐ Object Equivalency - Structural comparison with property exclusion/inclusion
- ๐ LINQ Operations - Enhanced diagnostics for Contains/Any/All operations
- โก Async/Await Support - Full support for async assertions with value diagnostics
- ๐ซ Dynamic Types - Dynamic objects support (Expando)
Live Examples
See demo for assertion example output.
Quick Start
1. Install Package
dotnet add package SharpAssert
2. Using SharpAssert
using static SharpAssert.Sharp;
[Test]
public void Should_be_equal()
{
var expected = 4;
var actual = 5;
Assert(expected == actual);
// Assertion failed: expected == actual
// Left: 4
// Right: 5
// Result: false
}
Features in Detail
String Comparisons
Character-level diffs powered by DiffPlex:
var actual = "hello";
var expected = "hallo";
Assert(actual == expected);
// Assertion failed: actual == expected
// String diff (inline):
// h[-e][+a]llo
Multiline string diffs:
var actual = "line1\nline2\nline3";
var expected = "line1\nMODIFIED\nline3";
Assert(actual == expected);
// Assertion failed: actual == expected
// String diff:
// line1
// - line2
// + MODIFIED
// line3
Collection Comparisons
First mismatch and missing/extra elements:
var actual = new[] { 1, 2, 3, 5 };
var expected = new[] { 1, 2, 4, 5 };
Assert(actual.SequenceEqual(expected));
// Assertion failed: actual.SequenceEqual(expected)
// Collections differ at index 2:
// Expected: 4
// Actual: 3
String Pattern Matching
Wildcard patterns with * (any sequence) and ? (single character):
using SharpAssert.Features.Strings;
Assert("hello world".Matches("hello *")); // * matches any sequence
Assert("test.txt".Matches("*.txt")); // File extension matching
Assert("test".Matches("t?st")); // ? matches single character
Assert("HELLO".MatchesIgnoringCase("hello")); // Case-insensitive
Count substring occurrences:
Assert("error at line 5, error at line 10".Contains("error", Occur.Exactly(2)));
Assert("warn, warn, warn".Contains("warn", Occur.AtLeast(2)));
Assert("info".Contains("info", Occur.AtMost(1)));
Regex pattern occurrence counting:
Assert("test123 and test456".MatchesRegex(@"test\d+", Occur.Exactly(2)));
Collection Ordering
Validate collections are sorted:
using SharpAssert.Features.Collections;
var ascending = new[] { 1, 2, 3, 4 };
Assert(ascending.IsInAscendingOrder());
var descending = new[] { 4, 3, 2, 1 };
Assert(descending.IsInDescendingOrder());
// With custom comparer
Assert(names.IsInAscendingOrder(StringComparer.OrdinalIgnoreCase));
Collection Uniqueness
Validate collections contain no duplicates:
using SharpAssert.Features.Collections;
var unique = new[] { 1, 2, 3, 4 };
Assert(unique.AllUnique());
// Uniqueness by property
var users = new[] { user1, user2, user3 };
Assert(users.AllUnique(u => u.Email));
// Custom equality comparer
Assert(items.AllUnique(StringComparer.OrdinalIgnoreCase));
Object Deep Comparison
Property-level diffs powered by Compare-Net-Objects:
var actual = new User { Name = "John", Age = 30, City = "NYC" };
var expected = new User { Name = "John", Age = 25, City = "LA" };
Assert(actual == expected);
// Assertion failed: actual == expected
// Object differences:
// Age: 30 โ 25
// City: "NYC" โ "LA"
Object Equivalency
Structural comparison with fluent configuration:
var actual = new Person { Name = "John", Age = 30, Id = 1 };
var expected = new Person { Name = "John", Age = 30, Id = 2 };
// Basic equivalency check
Assert(actual.IsEquivalentTo(expected));
// Exclude specific properties
Assert(actual.IsEquivalentTo(expected, config => config.Excluding(p => p.Id)));
// Include only specific properties
Assert(actual.IsEquivalentTo(expected, config => config.Including(p => p.Name)));
// Ignore collection ordering
var team1 = new Team { Members = new[] { "Alice", "Bob" } };
var team2 = new Team { Members = new[] { "Bob", "Alice" } };
Assert(team1.IsEquivalentTo(team2, config => config.WithoutStrictOrdering()));
LINQ Operations
Enhanced diagnostics for Contains, Any, All:
var users = new[] { "Alice", "Bob", "Charlie" };
Assert(users.Contains("David"));
// Assertion failed: users.Contains("David")
// Collection: ["Alice", "Bob", "Charlie"]
// Looking for: "David"
// Result: false
Async/Await Support
Full support for async expressions:
Assert(await client.GetAsync() == await server.GetAsync());
// Assertion failed: await client.GetAsync() == await server.GetAsync()
// Left: { Id: 1, Name: "Client" }
// Right: { Id: 2, Name: "Server" }
// Result: false
Exception Testing
Test expected exceptions with Throws<T> and ThrowsAsync<T>:
// Positive assertion - expects exception
Assert(Throws<ArgumentException>(() => throw new ArgumentException("invalid")));
// Negative assertion - expects no exception
Assert(!Throws<ArgumentException>(() => { /* no exception */ }));
// Access exception properties
var ex = Throws<ArgumentNullException>(() => throw new ArgumentNullException("param"));
Assert(ex.Message.Contains("param"));
// Async version
Assert(await ThrowsAsync<InvalidOperationException>(() =>
Task.Run(() => throw new InvalidOperationException())));
Custom Expectations
Create reusable expectations by inheriting from Expectation and returning an EvaluationResult.
Recommended convention for external/custom expectations:
- Suffix the type with
Expectation(e.g.,IsEvenExpectation) - For unary expectations, prefer a static factory method so call sites can use
using static(e.g.,Assert(IsEven(4))) - For expectations that take a primary value and additional parameters, prefer extension methods for fluent call sites (e.g.,
Assert(actual.IsEquivalentTo(expected)))
sealed class IsEvenExpectation(int value) : Expectation
{
public override EvaluationResult Evaluate(ExpectationContext context) =>
value % 2 == 0
? ExpectationResults.Pass(context.Expression)
: ExpectationResults.Fail(context.Expression, $"Expected even, got {value}");
}
static class Expectations
{
public static IsEvenExpectation IsEven(int value) => new(value);
}
using static Expectations;
Assert(IsEven(4));
Assert(!IsEven(5));
static class ExpectationExtensions
{
public static IsEvenExpectation IsEven(this int value) => new(value);
}
Assert(4.IsEven() & !5.IsEven());
Custom Error Messages
Assert(user.IsActive, $"User {user.Name} should be active for this operation");
Complex Expression Analysis
var order = new Order { Items = new[] { "Coffee", "Tea" }, Total = 15.50m };
var expectedTotal = 12.00m;
Assert(order.Items.Length > 0 && order.Total == expectedTotal);
// Assertion failed: order.Items.Length > 0 && order.Total == expectedTotal
// order.Items.Length > 0 โ True (Length: 2)
// order.Total == expectedTotal โ False
// order.Total: 15.50
// expectedTotal: 12.00
// Result: False
Architecture
SharpAssert is built on modern .NET technologies:
- MSBuild Source Rewriting - Compile-time code transformation
- Roslyn Syntax Analysis - Advanced C# code parsing and generation
- Expression Trees - Runtime expression analysis for rich diagnostics
- DiffPlex - String and sequence diffs
- CompareNETObjects - Deep object comparison
- CallerArgumentExpression - Fallback for edge cases
Requirements
- .NET 9.0 or later
- Test frameworks: xUnit, NUnit, or MSTest
Packages
SharpAssert consists of two NuGet packages:
- SharpAssert - Main package with MSBuild rewriter (install this one)
- SharpAssert.Runtime - Core assertion library (automatically included as dependency)
When you install SharpAssert, you get everything you need. The runtime package is a transitive dependency and requires no separate installation.
Configuration
Diagnostic Logging
Enable detailed rewriter diagnostics:
<PropertyGroup>
<SharpAssertEmitRewriteInfo>true</SharpAssertEmitRewriteInfo>
</PropertyGroup>
Performance
SharpAssert is designed for minimal overhead:
- Passing tests: Near-zero overhead - only the assertion check itself
- Failing tests: Rich diagnostics are computed only when assertions fail
- Expression evaluation: Each sub-expression evaluated exactly once (cached)
- Build time: Negligible impact - rewriter processes only test files
The rich diagnostic tools (object diffing, collection comparison) are only invoked on failure. This means your passing tests run at full speed, and the diagnostic cost is only paid when you need to understand a failure.
Known Issues
- Collection initializers cannot be used in expression trees (C# compiler limitation)
- Use
new[]{1,2,3}instead of[1, 2, 3]
- Use
Troubleshooting
Rewriting not working
- Verify
SharpAssertpackage is installed (SharpAssert.Runtime comes automatically) - Ensure
using static SharpAssert.Sharp;import - Clean and rebuild:
dotnet clean && dotnet build
No detailed error messages
- Check build output contains: "SharpAssert: Rewriting X source files"
- Verify rewritten files exist in
obj/Debug/net9.0/SharpRewritten/ - Ensure
SharpInternal.Assertcalls are being made (check generated code) - Look for #line directives in generated files
Enable diagnostic logging
For troubleshooting rewriter issues:
<PropertyGroup>
<SharpAssertEmitRewriteInfo>true</SharpAssertEmitRewriteInfo>
</PropertyGroup>
Then rebuild with verbose output: dotnet build -v detailed
Contributing
We welcome contributions! Please see our comprehensive Contributing Guide for:
- ๐ Quick start guide for developers
- ๐งช Testing strategy and workflow
- ๐ฆ Package versioning best practices
- ๐ง Development tips and debugging help
- ๐ Commit guidelines and release process
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- CompareNETObjects (>= 4.84.0)
- DiffPlex (>= 1.8.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on SharpAssert.Runtime:
| Package | Downloads |
|---|---|
|
SharpAssert
A pytest inspired assertion library for .NET with no special syntax. |
GitHub repositories
This package is not used by any popular GitHub repositories.