xUnit Assertions Overview
Table of contents
- What are xUnit Assertions?
- Installation
- Quick Start
- Assertion Styles
- Documentation Navigation
- Key Features
- Design Philosophy
- Failure Messages
- Best Practices
- See Also
- License
What are xUnit Assertions?
The UnambitiousFx.Core.XUnit
library provides assertion helpers for testing Result
, Option
, and Either
types with xUnit.net v3. These helpers offer both classic assertion styles and fluent/predicate-based chaining for expressive, low-ceremony tests.
Why use specialized assertions?
- Type-safe testing: Assert on functional types without manual unwrapping
- Fluent syntax: Chain assertions for readable test flows
- Predicate-based validation: Express invariants concisely
- Async support: First-class support for Task and ValueTask
- Clear failure messages: Concise, actionable test failure reports
Installation
Project Reference
Add a project reference (recommended when working inside the mono-repo):
<ProjectReference Include="..\..\..\src\Core.XUnit\Core.XUnit.csproj" />
NuGet Package
Or add the NuGet package (when published):
<PackageReference Include="UnambitiousFx.Core.XUnit" Version="x.y.z" />
Quick Start
Add the namespace (a global using
is convenient):
using UnambitiousFx.Core.XUnit;
Classic Style
var result = Result.Success(42);
result.ShouldBeSuccess(out var value);
Assert.Equal(42, value);
Fluent Style
Result.Success(42)
.EnsureSuccess()
.And(v => Assert.Equal(42, v))
.Map(v => v + 1)
.And(v => Assert.Equal(43, v));
Predicate Style
Result.Success(42).ShouldBeSuccessWhere(v => v > 40);
Option.Some(10).ShouldBeSomeWhere(v => v % 5 == 0);
Either<int,string>.FromLeft(2).ShouldBeLeftWhere(l => l < 5);
Assertion Styles
The library provides three complementary assertion styles:
1. Classic Assertions
Traditional assertion methods that extract values using out
parameters:
- Simple and straightforward
- Familiar to xUnit users
- Good for basic value extraction
Example:
Result.Success(42).ShouldBeSuccess(out var value);
Assert.Equal(42, value);
Learn more about classic assertions →
2. Fluent Assertions
Chainable assertions that return wrapper types for composable testing:
- Enables method chaining
- Supports mapping and filtering
- Keeps test flow readable
Example:
Result.Success(42)
.EnsureSuccess()
.Map(v => v * 2)
.And(v => Assert.Equal(84, v));
Learn more about fluent assertions →
3. Predicate Assertions
Property-based assertions using lambda predicates:
- Concise invariant checks
- No manual value extraction
- Clear failure messages
Example:
Result.Success(42).ShouldBeSuccessWhere(v => v > 40);
Learn more about predicate assertions →
Documentation Navigation
Explore xUnit Assertions features in depth:
- Result Assertions - Testing Result types with classic and fluent assertions
- Option Assertions - Testing Option types for Some/None cases
- Either Assertions - Testing Either types for Left/Right branches
- Predicate Assertions - Property-based testing with predicates
- Async Assertions - Testing Task and ValueTask with assertions
Key Features
Multi-Arity Result Support
Test Result types with multiple values (Result<T1, T2, …>):
Result.Success(1, "a", true)
.ShouldBeSuccess((i, s, b) => {
Assert.Equal(1, i);
Assert.Equal("a", s);
Assert.True(b);
});
Async/Await Support
All assertions have async variants for Task and ValueTask:
await Task.FromResult(Result.Success(42))
.ShouldBeSuccess(out var value);
await GetUserAsync()
.EnsureSuccess()
.And(user => Assert.Equal("John", user.Name));
Allocation-Light Design
Wrapper types are readonly struct
to minimize allocations:
// SuccessAssertion<T> is a struct wrapper
var assertion = Result.Success(42).EnsureSuccess();
assertion.Map(v => v * 2).And(v => Assert.Equal(84, v));
Source-Generated Predicates
Result predicate methods are source-generated for all arities (1-8):
// Generated for Result<T1>, Result<T1,T2>, ..., Result<T1,...,T8>
Result.Success(1, 2, 3)
.ShouldBeSuccessWhere(t => t.Item1 + t.Item2 + t.Item3 == 6);
Design Philosophy
The xUnit assertion helpers follow these principles:
- Type Safety: Leverage the type system to prevent testing mistakes
- Expressiveness: Make test intent clear and readable
- Performance: Minimize allocations with struct wrappers
- Consistency: Uniform API across Result, Option, and Either
- Composability: Support chaining and transformation within tests
Failure Messages
All helpers emit concise failure messages for quick diagnosis:
// Example failure output:
// "Expected success result but was failure with message: Invalid input"
Result.Failure<int>(new Exception("Invalid input"))
.ShouldBeSuccess();
Messages are designed to balance clarity and brevity for high test signal.
Best Practices
1. Choose the Right Style
- Use classic for simple value extraction
- Use fluent for complex test flows with transformations
- Use predicate for concise property validation
2. Add Context with Messages
Use the optional because
parameter for clarity:
Result.Success(5)
.ShouldBeSuccessWhere(v => v == 5, "Expected computed value to be 5");
3. Leverage Async Variants
Use async assertions for async operations:
await GetUserAsync()
.ShouldBeSuccessWhereAsync(user => user.Age >= 18);
4. Chain Fluent Assertions
Build readable test flows:
Result.Success(50)
.EnsureSuccess()
.Where(v => v == 50)
.Map(v => v + 1)
.Where(v => v == 51)
.And(v => Assert.Equal(51, v));
See Also
- Result - Core Result type documentation
- Option - Core Option type documentation
- Either - Core Either type documentation
License
MIT