Releases: yevhen/sharp.assert
v1.4.0
New Features
Collection Quantifiers
A complete suite of collection quantifier expectations for testing element conditions:
using SharpAssert;
// All items must match
Assert(items.Each(x => x > 0));
Assert(items.Each(x => x.IsEquivalentTo(template)));
// At least one must match
Assert(items.Some(x => x.Matches("error*")));
// No items may match
Assert(items.None(x => x == null));
// Exactly one must match
Assert(items.One(x => x.Id == targetId));
// Count-based quantifiers
Assert(items.Exactly(3, x => x.Score > 90));
Assert(items.AtLeast(2, x => x.IsEquivalentTo(admin)));
Assert(items.AtMost(1, x => x.Matches("*error*")));All quantifiers support both predicate expressions (x => x > 5) and custom expectations (x => x.IsEquivalentTo(expected)).
Lambda-Based Custom Expectations
New Expectation.From() factory eliminates boilerplate when creating custom expectations:
public static Expectation IsEven(this int value) =>
Expectation.From(
() => value % 2 == 0,
() => [$"Expected even number, got {value}"]
);
Assert(4.IsEven());Multiple Assertions with &&
Logical && now evaluates ALL operands and shows all failures - no short-circuit:
Assert(x == 5 && y == 10);
// Shows both failures when both operands are false:
// &&: Both operands were falseReplaces verbose Assert.Multiple() / AssertionScope patterns with native C# syntax.
Full Changelog: v1.3.0...v1.4.0
v1.3.0
New Features
Collection Assertions
ContainsInOrder(seq)- validates elements appear in order (gaps allowed)ContainsInConsecutiveOrder(seq)- validates elements appear consecutively (no gaps)IsSubsetOf(superset)- validates collection is a subsetIsSupersetOf(subset)- validates collection is a supersetIntersects(other)- validates collections share at least one elementSatisfies(p1, p2, ...)- validates elements satisfy predicates with unique 1:1 matching (bipartite matching)
Proximity Assertions
BeCloseTo(target, tolerance)- validates numeric values within toleranceBeApproximately(target, tolerance)- alias for BeCloseToBeCloseTo(expected, TimeSpan)- validates DateTime/DateTimeOffset within tolerance
IsEquivalentTo Enhancements
- Full feature parity with FluentAssertions configuration options
Usage
using SharpAssert.Features.Collections;
using SharpAssert.Features.Proximity;
// Collection assertions
Assert(coll.ContainsInOrder(1, 3, 5));
Assert(coll.ContainsInConsecutiveOrder(2, 3, 4));
Assert(coll.IsSubsetOf(superset));
Assert(coll.IsSupersetOf(subset));
Assert(coll.Intersects(other));
Assert(users.Satisfies(u => u.Age > 18, u => u.IsAdmin));
// Proximity assertions
Assert(value.BeCloseTo(target, 0.01));
Assert(value.BeApproximately(pi, 0.001));
Assert(dt.BeCloseTo(expected, TimeSpan.FromMilliseconds(100)));Full Changelog: v1.2.0...v1.3.0
v1.2.0
What's New
Custom Expectations
Create reusable, composable expectations by inheriting from Expectation:
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}");
}
// Use with static factories or extension methods
Assert(IsEven(4));
Assert(4.IsEven() & !5.IsEven());String Pattern Matching
Wildcard patterns with * (any sequence) and ? (single character):
using SharpAssert.Features.Strings;
Assert("hello world".Matches("hello *"));
Assert("test.txt".Matches("*.txt"));
Assert("HELLO".MatchesIgnoringCase("hello"));Occurrence counting for substrings and regex:
Assert("error, error".Contains("error", Occur.Exactly(2)));
Assert("warn, warn".Contains("warn", Occur.AtLeast(1)));
Assert("test123 test456".MatchesRegex(@"test\d+", Occur.Exactly(2)));Collection Ordering
Validate collections are sorted:
using SharpAssert.Features.Collections;
Assert(numbers.IsInAscendingOrder());
Assert(scores.IsInDescendingOrder());
Assert(names.IsInAscendingOrder(StringComparer.OrdinalIgnoreCase));Collection Uniqueness
Validate collections contain no duplicates:
Assert(items.AllUnique());
Assert(users.AllUnique(u => u.Email)); // By keyObject Equivalency
Structural comparison with fluent configuration:
Assert(actual.IsEquivalentTo(expected));
Assert(actual.IsEquivalentTo(expected, c => c.Excluding(p => p.Id)));
Assert(actual.IsEquivalentTo(expected, c => c.Including(p => p.Name)));
Assert(team1.IsEquivalentTo(team2, c => c.WithoutStrictOrdering()));Improvements
- Programmatic access to results -
SharpAssertionException.Resultproperty provides structured access to assertion evaluation results - Hardened expression evaluation - Better fallbacks and sentinels for edge cases in expression compilation
- Improved expression display - Fixed display for complex nested binary operations and nullable unwrapping
Internal
- Feature-based code organization
- Decoupled evaluation from formatting
- Structured comparison results with rendering pushed into record types
- Simplified rendering without visitors
Full Changelog: v1.1.0...v1.2.0
Release 1.1.0
v1.1.0 Tidy up CONSTITUTION.md
Release 1.0.6
v1.0.6 Fix(build): Correct MSBuild targets for cross-platform compatibility
Release 1.0.5
v1.0.5 Add project brief
Release 1.0.4
Fix MSBuild targets to preserve unprocessed source files The previous implementation removed ALL source files from compilation and only included rewritten files. This broke namespace resolution for types in files without Assert() calls (like PipeMock). This fix: - Only removes files that were actually rewritten - Includes both rewritten files AND unprocessed source files - Preserves the complete compilation context Fixes namespace resolution errors like: CS0103: The name 'PipeMock' does not exist in the current context 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>