Introduction
Writing unit tests is easy with D. The unittest block allows you to start writing tests and be productive with no special setup.
Unfortunately the assert expression does not help you write expressive asserts, and when a failure occurs it’s hard to understand why. fluent-asserts allows you to more naturally specify the expected outcome of a TDD or BDD-style test.
Quick Start
Add the dependency:
dub add fluent-assertsWrite your first test:
unittest { true.should.equal(false).because("this is a failing assert");}
unittest { Assert.equal(true, false, "this is a failing assert");}Run the tests:
dub testFeatures
fluent-asserts is packed with capabilities to make your tests expressive, fast, and easy to debug.
Readable Assertions
Write tests that read like sentences. The fluent API chains naturally, making your test intent crystal clear.
expect(user.age).to.be.greaterThan(18);expect(response.status).to.equal(200);expect(items).to.contain("apple").and.not.beEmpty();Rich Assertion Library
A comprehensive set of built-in assertions for all common scenarios:
- Equality:
equal,approximately - Comparison:
greaterThan,lessThan,between,within - Strings:
contain,startWith,endWith,match - Collections:
contain,containOnly,beEmpty,beSorted - Types:
beNull,instanceOf - Exceptions:
throwException,throwAnyException - Memory:
allocateGCMemory,allocateNonGCMemory - Timing:
haveExecutionTime
See the API Reference for the complete list.
Detailed Failure Messages
When assertions fail, you get all the context you need to understand what went wrong:
ASSERTION FAILED: user.age should be greater than 18.OPERATION: greaterThan
ACTUAL: <int> 16EXPECTED: <int> greater than 18
source/test.d:42> 42: expect(user.age).to.be.greaterThan(18);Multiple Output Formats
Choose the right format for your environment:
- Verbose - Human-friendly with full context for local development
- TAP - Universal machine-readable format for CI/CD pipelines
- Compact - Token-optimized for AI coding assistants
See Configuration for details.
Context Data for Debugging
When testing in loops or complex scenarios, attach context to pinpoint failures:
foreach (i, user; users) { user.isValid.should.beTrue .withContext("index", i) .withContext("userId", user.id) .because("user %s failed validation", user.name);}See Context Data for more examples.
Memory Allocation Testing
Verify that your code behaves correctly with respect to memory:
// Ensure code allocates GC memoryexpect({ auto arr = new int[100]; }).to.allocateGCMemory();
// Ensure code is @nogc compliantexpect({ int x = 42; }).not.to.allocateGCMemory();@nogc Internals
The library uses @nogc compatible data structures internally (HeapString, FixedAppender) to minimize GC pressure during test execution. This means assertions run efficiently without triggering garbage collection in hot paths.
Zero Overhead in Release Builds
Like D’s built-in assert, fluent-asserts becomes a complete no-op in release builds. Use it freely in production code with zero runtime cost.
Assertion Statistics
Track how your tests are performing:
auto stats = Lifecycle.instance.statistics;writeln("Passed: ", stats.passedAssertions);writeln("Failed: ", stats.failedAssertions);See Assertion Statistics for details.
Extensible
Create custom assertions for your domain and register custom serializers for your types. See Extending for how to build on top of fluent-asserts.
The API
The library provides three ways to write assertions: expect, should, and Assert.
expect
expect is the main assertion function. It takes a value to test and returns a fluent assertion object.
expect(testedValue).to.equal(42);Use not to negate and because to add context:
expect(testedValue).to.not.equal(42);expect(true).to.equal(false).because("of test reasons");// Output: Because of test reasons, true should equal `false`.should
should works with UFCS for a more natural reading style. It’s an alias for expect:
// These are equivalenttestedValue.should.equal(42);expect(testedValue).to.equal(42);Assert
Assert provides a traditional assertion syntax:
// These are equivalentexpect(testedValue).to.equal(42);Assert.equal(testedValue, 42);
// Negate with "not" prefixAssert.notEqual(testedValue, 42);Recording Evaluations
The recordEvaluation function captures assertion results without throwing on failure. This is useful for testing assertion behavior or inspecting results programmatically.
import fluentasserts.core.lifecycle : recordEvaluation;
unittest { auto evaluation = ({ expect(5).to.equal(10); }).recordEvaluation;
// Inspect the evaluation result (use [] to access FixedAppender content) assert(evaluation.result.expected[] == "10"); assert(evaluation.result.actual[] == "5");}The Evaluation.result provides access to:
expected- the expected value (use[]to get the string)actual- the actual value (use[]to get the string)negated- whether the assertion was negated with.notmissing- array of missing elements (for collection comparisons)extra- array of extra elements (for collection comparisons)messages- the assertion message segments
Release Builds
Like D’s built-in assert, fluent-asserts assertions are disabled in release builds. This means you can use them in production code without runtime overhead:
// In debug builds: assertion is checked// In release builds: this is a no-opexpect(value).to.be.greaterThan(0);See Configuration for how to control this behavior.
Custom Assert Handler
During unittest builds, the library automatically installs a custom handler for D’s built-in assert statements. This provides fluent-asserts style error messages even when using standard assert:
unittest { assert(1 == 2, "math is broken"); // Output includes ACTUAL/EXPECTED formatting and source location}The handler is only active during version(unittest) builds. To temporarily disable it:
import core.exception;
auto savedHandler = core.exception.assertHandler;scope(exit) core.exception.assertHandler = savedHandler;core.exception.assertHandler = null;Next Steps
- See the Installation guide for detailed setup
- Learn about Assertion Styles in depth
- Explore the API Reference for all available operations
- Understand the Philosophy behind fluent-asserts
- Upgrading from v1? See Upgrading to v2.0.0