Skip to content

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 @nogc data 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.equalfluentasserts.operations.equality.equal
fluentasserts.core.operations.containfluentasserts.operations.string.contain
fluentasserts.core.operations.greaterThanfluentasserts.operations.comparison.greaterThan
fluentasserts.core.operations.throwablefluentasserts.operations.exception.throwable
fluentasserts.core.operations.registryfluentasserts.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 check
static if (fluentAssertsEnabled) { /* ... */ }
// Runtime configuration
config.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
Terminal window
# Use compact format for AI-assisted development
CLAUDECODE=1 dub test

See 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 Context
result.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 GC
expect({ auto arr = new int[100]; }).to.allocateGCMemory();
// Ensure code is @nogc compliant
expect({ 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 failing
assert(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:

Learn More

Explore the documentation to get the most out of fluent-asserts v2: