Upgrading to v2.0.0
Welcome to fluent-asserts v2.0!
This release is a major milestone. We have completely rewritten the internal engine to be faster, lighter, and more flexible. While the public API remains largely familiar, the internal architecture has shifted significantly to use manual memory management and reduce GC pressure.
This guide covers everything you need to know to upgrade your projects.
The Big Architectural Shifts
Before diving into the code, it helps to understand why things changed.
1. Memory Management Overhaul
The most significant change in v2 is the move from D’s Garbage Collector (GC) to manual memory management. While version 1 relied on the GC for dynamic allocations and assertion strings, version 2 utilizes HeapString and HeapData with Reference Counting.
This shift brings several key benefits:
- Reduced GC pressure: Internal operations use
@nogcdata structures, minimizing garbage collection during tests - Performance: Small Buffer Optimization (SBO) avoids allocations entirely for short strings
- Lazy parsing: Source code parsing only happens if an assertion fails, keeping your passing tests fast
2. The New Evaluation Pipeline
We moved from a class-based evaluation system to a lightweight struct-based system. Evaluation is now a struct, and results use AssertResult instead of the old interface-based system.
Breaking Changes
If you are upgrading an existing codebase, watch out for these structural changes.
Module Restructuring
We cleaned up the module hierarchy to be more logical.
| Old Location (v1) | New Location (v2) |
|---|---|
fluentasserts.core.operations.equal | fluentasserts.operations.equality.equal |
fluentasserts.core.operations.contain | fluentasserts.operations.string.contain |
fluentasserts.core.operations.greaterThan | fluentasserts.operations.comparison.greaterThan |
fluentasserts.core.operations.throwable | fluentasserts.operations.exception.throwable |
fluentasserts.core.operations.registry | fluentasserts.operations.registry |
Serializers
v2 uses HeapSerializerRegistry for @nogc compatible serialization.
New Features
1. Centralized Configuration
You now have a unified FluentAssertsConfig for both compile-time and runtime settings.
import fluentasserts.core.config;
// Compile-time checkstatic if (fluentAssertsEnabled) { /* ... */ }
// Runtime configurationconfig.output.setFormat(OutputFormat.compact);2. Output Formats
Three output formats let you tailor assertion messages for your audience:
- Verbose (default) - Human-friendly format with full context and source snippets
- TAP - Universal machine-readable format for CI/CD pipelines and test harnesses
- Compact - Token-optimized format for AI coding assistants like Claude Code
# Use compact format for AI-assisted developmentCLAUDECODE=1 dub testSee Configuration for all the ways to set output formats.
3. Context Data & Formatted Messages
When debugging loops, you can now add context directly to the assertion chain.
// Formatted "Because"foreach (i; 0 .. 100) { result.should.equal(expected).because("iteration %s", i);}
// Key-Value Contextresult.should.equal(expected) .withContext("userId", userId) .withContext("email", user.email);See Context Data for more examples.
4. Memory Assertions
You can strictly test your memory allocation behavior to ensure code uses the GC or remains @nogc compliant.
// Ensure code uses GCexpect({ auto arr = new int[100]; }).to.allocateGCMemory();
// Ensure code is @nogc compliantexpect({ int x = 42; }).not.to.allocateGCMemory();See Memory Assertions for platform notes.
Migration Guide
Follow these steps to upgrade your application to v2.0.
Step 1: Update Imports
Most users will not need to take any action. Advanced users who import internal modules will need to update them to the new paths found in the breaking changes section.
Step 2: Update Custom Serializers
If you registered custom serializers in v1, use HeapSerializerRegistry:
HeapSerializerRegistry.instance.register!MyType(&mySerializer);Step 3: Update Custom Operations
If you wrote custom operations, note that the signature has changed. Operations now modify the Evaluation struct directly instead of returning IResult[].
See Extending fluent-asserts for the new patterns and examples.
More New Features
Assertion Statistics
Track how many assertions pass and fail across your test suite:
auto stats = Lifecycle.instance.statistics;writeln("Passed: ", stats.passedAssertions);writeln("Failed: ", stats.failedAssertions);See Assertion Statistics for detailed usage.
Recording Evaluations
Capture assertion results programmatically without throwing:
auto evaluation = ({ expect(5).to.equal(10);}).recordEvaluation;
// Inspect without failingassert(evaluation.result.expected[] == "10");See Recording Evaluations for more details.
Release Build Behavior
Like D’s built-in assert, fluent-asserts becomes a no-op in release builds for zero runtime overhead. You can override this with version flags.
See Release Build Configuration for details.
Getting Help
If you get stuck:
- Check the API Reference for current method signatures
- Examine the source code in
source/fluentasserts/operations/for examples - Open a ticket at fluent-asserts/issues
Learn More
Explore the documentation to get the most out of fluent-asserts v2:
- Installation - Get started with fluent-asserts
- Assertion Styles - Learn the fluent API
- Configuration - Output formats and build settings
- Context Data - Debug assertions in loops
- Assertion Statistics - Track pass/fail metrics
- Memory Management - Understand the
@nogcinternals - Core Concepts - How the evaluation pipeline works
- Extending - Create custom operations and serializers
- API Reference - Complete operation reference