-
Notifications
You must be signed in to change notification settings - Fork 1
Assertion Tracking
Added in v2.28.30
Assertion tracking renders your test assertions as styled inline notes in sequence diagrams. Passing assertions appear as green notes (✓), and failing assertions appear as red notes (✗) with the failure message.
This makes it immediately visible what was verified at each point in a test flow — directly inside the diagram, without navigating to test code.
Added in v2.30.0
The recommended approach is the TestTrackingDiagrams.AssertionRewriter package — an MSBuild task that automatically wraps your .Should() assertions at compile time. No changes to your test code required.
Requirements: The AssertionRewriter only works with FluentAssertions or AwesomeAssertions (i.e. assertions using the
.Should()API). If you use a different assertion library, see Manual Tracking below.
SDK requirement: The AssertionRewriter MSBuild task requires the .NET 9 SDK or later (it depends on Roslyn 4.12.0 which ships with .NET 9). Your projects can still target
net8.0or earlier — only the build SDK needs to be 9.0+. If yourglobal.jsonpins to an 8.x SDK, update it to"version": "9.0.0"with"rollForward": "latestFeature".
Compatibility: The AssertionRewriter operates as an MSBuild task that rewrites source files before compilation (
BeforeTargets="CoreCompile"). It does not participate in the Roslyn source generator pipeline, so it coexists cleanly with all source generators (ReqNRoll, Blazor Razor,RegexGenerator, etc.) and other MSBuild-based code generation tools.
- Install the package:
<PackageReference Include="TestTrackingDiagrams.AssertionRewriter" Version="2.30.0" />- Add the assembly-level opt-in attribute anywhere in your test project (commonly in a
GlobalUsings.csorAssertionTracking.csfile):
using TestTrackingDiagrams.Tracking;
[assembly: TrackAssertions]The
TrackAssertionsAttributeandSuppressAssertionTrackingAttributetypes are auto-generated into your project at build time — no manual type definitions needed.
That's it. Your existing assertions now appear in diagrams automatically:
[Fact]
public async Task Creates_order_and_returns_201()
{
var response = await _client.PostAsJsonAsync("/orders", new { Item = "Widget", Qty = 3 });
response.StatusCode.Should().Be(HttpStatusCode.Created); // ✓ appears in diagram
var order = await response.Content.ReadFromJsonAsync<Order>();
order!.Item.Should().Be("Widget"); // ✓ appears in diagram
order!.Qty.Should().Be(3); // ✓ appears in diagram
}The MSBuild task runs before compilation (BeforeTargets="CoreCompile") and:
- Auto-generates the
TrackAssertionsAttributeandSuppressAssertionTrackingAttributetypes into your project's intermediate output - Scans for the
[assembly: TrackAssertions]attribute in your source files - Finds all expression statements containing
.Should()calls - Wraps them in
Track.That(() => ...)(orTrack.ThatAsync(async () => ...)for awaited assertions) - Adds
using TestTrackingDiagrams.Tracking;if not already present
Your source code stays unchanged — the wrapping is only applied to the compiled output.
Before (your source code):
response.StatusCode.Should().Be(HttpStatusCode.OK);
await result.Should().BeEquivalentToAsync(expected);After (at compile time):
Track.That(() => response.StatusCode.Should().Be(HttpStatusCode.OK));
await Track.ThatAsync(async () => await result.Should().BeEquivalentToAsync(expected));You can selectively opt out of assertion wrapping using either attributes or pragma comments.
Apply [SuppressAssertionTracking] to a method or class to skip all assertions within:
using TestTrackingDiagrams.Tracking;
[SuppressAssertionTracking]
public void HelperMethod()
{
// Assertions in here won't be wrapped
result.Should().NotBeNull();
}
[SuppressAssertionTracking]
public class InfrastructureTests
{
// No assertions in this class are wrapped
}Use // pragma:TrackAssertions:disable and // pragma:TrackAssertions:enable comments for finer-grained control — either inline (single statement) or as a range (multiple statements).
Single statement — add the pragma as a trailing comment:
x.Should().Be(1); // pragma:TrackAssertions:disableRange — wrap a block of statements between disable/enable comments:
// pragma:TrackAssertions:disable
x.Should().Be(1); // ← not wrapped
y.Should().Be(2); // ← not wrapped
// pragma:TrackAssertions:enable
z.Should().Be(3); // ← this one gets wrapped normallyNote: If you use
// pragma:TrackAssertions:disablewithout a matching// pragma:TrackAssertions:enable, all subsequent assertions in that file will be skipped.
All project templates ship with the AssertionRewriter pre-configured — the [assembly: TrackAssertions] attribute and package reference are included by default.
If you can't use the AssertionRewriter (e.g. you use a different assertion library that doesn't have .Should()), you can manually wrap assertions with Track.That():
using TestTrackingDiagrams.Tracking;
[Fact]
public async Task Creates_order_and_returns_201()
{
var response = await _client.PostAsJsonAsync("/orders", new { Item = "Widget", Qty = 3 });
Track.That(() => response.StatusCode.Should().Be(HttpStatusCode.Created));
var order = await response.Content.ReadFromJsonAsync<Order>();
Track.That(() => order!.Item.Should().Be("Widget"));
Track.That(() => order!.Qty.Should().Be(3));
}This produces three green assertion notes in the sequence diagram:
- ✓ Response status code should be Created
- ✓ Order item should be Widget
- ✓ Order qty should be 3
All methods live in the TestTrackingDiagrams.Tracking namespace on the static Track class.
public static void That(
Action assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)Executes the assertion and logs an inline note. On failure, logs a red note and re-throws the exception.
public static T That<T>(
Func<T> assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)Same as above but returns the value produced by the expression. Useful when you want to capture a result and assert on it in one step:
var count = Track.That(() => items.Should().HaveCount(5).And.Subject.Count);public static async Task ThatAsync(
Func<Task> assertion,
[CallerArgumentExpression(nameof(assertion))] string? expression = null)Async variant for assertions that involve await:
await Track.ThatAsync(async () =>
{
var content = await response.Content.ReadAsStringAsync();
content.Should().Contain("success");
});Track.That() captures the expression text regardless of which assertion library you use:
// FluentAssertions/AwesomeAssertions
Track.That(() => result.Should().Be(42));
// xUnit Assert
Track.That(() => Assert.Equal(42, result));
// NUnit Assert
Track.That(() => Assert.That(result, Is.EqualTo(42)));
// Shouldly
Track.That(() => result.ShouldBe(42));The AssertionExpressionFormatter produces the best output for FluentAssertions/AwesomeAssertions .Should(). patterns. For other libraries, it falls back to displaying the raw expression text (which is still readable and useful).
-
Expression capture: C# 10's
[CallerArgumentExpression]captures the source text of the lambda passed toTrack.That()at compile time. -
Formatting:
AssertionExpressionFormattertransforms the raw expression (e.g.response.StatusCode.Should().Be(HttpStatusCode.Created)) into readable English (response status code should be Created). -
PlantUML injection: The formatted text is injected into the sequence diagram as a styled
hnote across <<assertionNote>>viaDefaultTrackingDiagramOverride.InsertPlantUml(). -
Conditional styling: A
<<assertionNote>>PlantUML style (smaller font, rounded corners) is only emitted when assertion notes are present in the diagram.
The formatter handles common FluentAssertions/AwesomeAssertions patterns:
| Raw Expression | Formatted Output |
|---|---|
result.Count.Should().Be(3) |
Result count should be 3 |
order.Status.Should().Be(OrderStatus.Shipped) |
Order status should be Shipped |
items.Should().HaveCount(5) |
Items should have count 5 |
response.Should().BeOfType<OrderResponse>() |
Response should be of type OrderResponse |
list.Should().ContainSingle(x => x.IsAdmin) |
List should contain single x => x.IsAdmin |
result.Should().NotBeNull() |
Result should not be null |
Rules applied:
- Splits on
.Should().— left side becomes the subject, right side becomes the predicate - PascalCase is split into space-separated words (
HaveCount→have count) - Enum prefixes are stripped for simple arguments (
HttpStatusCode.OK→OK) - Generic type arguments are preserved (
BeOfType<string>()→be of type string) -
.And.chains take only the first assertion - Lambdas in arguments are preserved as-is
When any test in the report uses Track.That(), an Assertions: Show / Hide toggle appears in the report toolbar (alongside the existing Details and Headers toggles).
- Default: Hidden — assertion notes are stripped from the diagram source before rendering
- Show: Re-renders all diagrams with assertion notes visible
- The toggle works at both report level (affects all diagrams) and scenario level (affects a single test)
No toggle when unused: If no tests in the report use
Track.That(), the Assertions toggle is not shown.
Track.That() resolves the current test identity using the following priority order:
-
Track.TestIdResolver(static delegate — set automatically by framework integrations: LightBDD, BDDfy, ReqNRoll) -
TestIdentityScope.Current(AsyncLocal — set viaTestIdentityScope.Begin()) -
TestIdentityScope.GlobalFallback(static — useful in single-threaded test runners)
If none of these resolve a test ID, the assertion executes normally but no diagram note is logged. This means Track.That() is safe to use in shared helper methods that may run outside a test context.
When using one of the framework adapter packages (TestTrackingDiagrams.LightBDD.*, TestTrackingDiagrams.BDDfy.*, TestTrackingDiagrams.ReqNRoll.*), Track.TestIdResolver is set up automatically during configuration — no manual wiring needed.
If you're using a framework that doesn't have a built-in adapter, set Track.TestIdResolver during test setup:
// Example: wire up xUnit v3 TestContext directly
Track.TestIdResolver = () => Xunit.TestContext.Current.Test?.UniqueID;Getting Started
Common Tasks
Integration Guides
- Integration xUnit3
- Integration xUnit2
- Integration NUnit
- Integration MSTest
- Integration TUnit
- Integration BDDfy xUnit3
- Integration LightBDD xUnit2
- Integration LightBDD xUnit3
- Integration LightBDD TUnit
- Integration ReqNRoll xUnit2
- Integration ReqNRoll xUnit3
- Integration ReqNRoll TUnit
Extensions
- Integration AtlasDataApi Extension
- Integration BigQuery Extension
- Integration Bigtable Extension
- Integration BlobStorage Extension
- Integration ClickHouse Extension
- Integration CloudStorage Extension
- Integration CosmosDB Extension
- Integration Dapper Extension
- Integration DynamoDB Extension
- Integration EF Core Relational Extension
- Integration Elasticsearch Extension
- Integration EventBridge Extension
- Integration EventHubs Extension
- Integration Grpc Extension
- Integration Kafka Extension
- Integration MassTransit Extension
- Integration MongoDB Extension
- Integration MySqlConnector Extension
- Integration Npgsql Extension
- Integration Oracle Extension
- Integration PubSub Extension
- Integration Redis Extension
- Integration S3 Extension
- Integration ServiceBus Extension
- Integration SNS Extension
- Integration Spanner Extension
- Integration SqlClient Extension
- Integration Sqlite Extension
- Integration SQS Extension
- Integration StorageQueues Extension
- Integration OpenTelemetry Extension
- Integration DispatchProxy Extension
- Integration MediatR Extension
- Integration PlantUML IKVM
Configuration
- Tracking Dependencies
- Tracking Custom Dependencies
- HTTP Tracking Setup
- Report Configuration
- Diagram Customisation
- Phase-Aware Tracking
- Content Formatting
- PlantUML Server Configuration
Features
- Generated Reports
- Search Syntax
- Component Diagrams
- PlantUML Browser Rendering
- Inline SVG Rendering
- Internal Flow Tracking
- Tags and Attributes
- Excluding Requests
- Excluded Headers
- Multi-Host Test Architectures
- Event-Driven Architecture Testing
- Service Bus Tracking Patterns
- Background Thread Correlation
- Parallel-Safe Background Correlation
- Event & Message Tracking
- Assertion Tracking
- Step Tracking
- Tabular Attributes
- Large Response and Diagram Handling
- Diagnostics and Debugging
- CI Summary Integration
- CI Artifact Upload
- Merging Parallel Reports
Reference