Assertivo 0.5.0
dotnet add package Assertivo --version 0.5.0
NuGet\Install-Package Assertivo -Version 0.5.0
<PackageReference Include="Assertivo" Version="0.5.0" />
<PackageVersion Include="Assertivo" Version="0.5.0" />
<PackageReference Include="Assertivo" />
paket add Assertivo --version 0.5.0
#r "nuget: Assertivo, 0.5.0"
#:package Assertivo@0.5.0
#addin nuget:?package=Assertivo&version=0.5.0
#tool nuget:?package=Assertivo&version=0.5.0
Assertivo
A fluent, strongly-typed assertion library for .NET with a .Should() entry-point pattern, zero-allocation happy paths, and AOT compatibility.

Disclaimer: Assertivo is a personal learning/playground project, built to explore .NET library design, AOT compatibility, source-generator-free fluent APIs, and Spec Kit capabilities. It is not affiliated with or endorsed by any commercial product, and is not recommended for production use without your own due diligence.
Quickstart
Add the using directive:
using Assertivo;
Write your first assertion:
[Fact]
public void My_first_assertion()
{
int result = 2 + 2;
result.Should().Be(4);
}
Run your tests:
dotnet test
Examples
Boolean assertions
bool isReady = true;
isReady.Should().BeTrue();
String assertions
string name = "Assertivo";
name.Should().Contain("Assert");
name.Should().NotBeEmpty(); // passes for any non-"" value, including null
name.Should().NotBeNullOrEmpty(); // fails on both null and ""
name.Should().NotContain("secret", "credentials must not be logged");
name.Should().NotBe("None");
Numeric assertions
int count = 10;
count.Should().BeGreaterThanOrEqualTo(5);
count.Should().BeGreaterThan(0);
count.Should().BeLessThan(100);
count.Should().BeLessThanOrEqualTo(10);
count.Should().NotBe(0);
// Chain comparisons in a single expression
count.Should().BeGreaterThan(0).And.BeLessThan(100);
Collection assertions
var items = new List<string> { "a", "b", "c" };
items.Should().HaveCount(3);
items.Should().Contain("b");
items.Should().BeEquivalentTo("c", "a", "b"); // order-independent
items.Should().NotBeEmpty();
var scores = new List<int> { 10, 20, 30 };
scores.Should().AllSatisfy(score => score.Should().BeGreaterThanOrEqualTo(10));
scores.Should().AllSatisfy((score, index) => score.Should().Be((index + 1) * 10));
Predicate collection containment
var orders = new[]
{
new { Id = 1, Status = "Pending" },
new { Id = 2, Status = "Shipped" }
};
// Passes when at least one element matches
orders.Should().Contain(o => o.Status == "Shipped");
// Supports chaining
orders.Should().Contain(o => o.Status == "Shipped").And.HaveCount(2);
Ordered sequence equality
// Inline values (params overload)
var result = GetSortedIds();
result.Should().Equal(10, 20, 30);
// IEnumerable overload with reason
var output = pipeline.Process(input);
output.Should().Equal(expectedOutput, because: "pipeline output must be deterministic");
// Custom comparer
var names = new[] { "Alice", "BOB" };
names.Should().Equal(new[] { "alice", "bob" }, comparer: StringComparer.OrdinalIgnoreCase);
Drill-down with .Which
var users = new List<User> { new("Alice") };
users.Should().ContainSingle()
.Which.Name.Should().Be("Alice");
Exception assertions
Action act = () => throw new ArgumentNullException("param");
act.Should().Throw<ArgumentNullException>()
.Which.ParamName.Should().Be("param");
Async exception assertions
Assert directly on a Task — no lambda wrapper required:
Task task = repository.SaveAsync(entity);
await task.Should().ThrowAsync<DbException>();
Chain further assertions on the caught exception via .Which:
Task<User> task = service.GetUserAsync(id);
var ex = await task.Should().ThrowAsync<NotFoundException>();
ex.Which.ResourceId.Should().Be(id);
Pass a because reason to provide context in CI output:
await task.Should().ThrowAsync<TimeoutException>(
"because the call must fail fast when the upstream is unavailable");
The Func<Task> entry point is still supported for cases where you need to capture the subject lazily:
Func<Task> act = async () => { await Task.Delay(1); throw new InvalidOperationException(); };
await act.Should().ThrowAsync<InvalidOperationException>();
Async iterator assertions
Assert directly on an IAsyncEnumerable<T> — no await foreach, no try/catch, no lambda wrapper:
IAsyncEnumerable<int> stream = repository.StreamOrdersAsync(customerId);
await stream.Should().ThrowAsync<UnauthorizedException>();
Chain further assertions on the caught exception via .Which:
IAsyncEnumerable<Event> events = feed.ReadAsync();
var ex = await events.Should().ThrowAsync<StreamClosedException>();
ex.Which.Reason.Should().Be("server disconnected");
Pass a because reason to provide context in CI output:
await stream.Should().ThrowAsync<RateLimitException>(
"because the quota for this tenant is exhausted");
AggregateException with a single inner exception is automatically unwrapped — the same behaviour as the Task and Func<Task> entry points:
// AggregateException(InvalidOperationException) → passes as InvalidOperationException
await stream.Should().ThrowAsync<InvalidOperationException>();
Object assertions
var a = new MyService();
var b = new MyService();
a.Should().NotBeSameAs(b); // different instances
a.Should().NotBeSameAs(b, "each request must get its own scope");
a.Should().NotBeSameAs(b).And.NotBeNull(); // chainable
Custom comparers
result.Should().Be(expected, StringComparer.OrdinalIgnoreCase);
items.Should().BeEquivalentTo(expected, myCustomComparer);
because reasons
logs.Should().NotContain("secret-key", "credentials must not be logged");
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.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.