Skip to content

Context Data

When assertions run in loops or complex scenarios, it’s helpful to know exactly which iteration or condition caused a failure. fluent-asserts provides two features for attaching context data to assertions.

Format-Style because

The because method now supports printf-style format strings, making it easy to include dynamic values in your failure messages:

import fluent.asserts;
unittest {
foreach (i; 0 .. 100) {
auto result = computeValue(i);
result.should.equal(expected).because("At iteration %s", i);
}
}

On failure, you’ll see:

Because At iteration 42, result should equal expected.

Multiple Format Arguments

You can include multiple values:

result.should.equal(expected).because("iteration %s of %s with seed %s", i, total, seed);

Format Specifiers

Standard D format specifiers work:

value.should.equal(0).because("value %.2f exceeded threshold", floatValue);
value.should.beTrue.because("flags: 0x%08X", bitmask);

Context Attachment with withContext

For more structured debugging data, use withContext to attach key-value pairs:

import fluent.asserts;
unittest {
foreach (userId; userIds) {
auto user = fetchUser(userId);
user.isActive.should.beTrue
.withContext("userId", userId)
.withContext("email", user.email);
}
}

Chaining Multiple Contexts

You can chain multiple withContext calls:

result.should.equal(expected)
.withContext("iteration", i)
.withContext("input", testInput)
.withContext("config", configName);

Context in Output

Context data is displayed differently based on the output format:

Verbose format:

ASSERTION FAILED: result should equal expected.
OPERATION: equal
CONTEXT:
userId = 42
email = test@example.com
ACTUAL: <bool> false
EXPECTED: <bool> true

Compact format:

FAIL: result should equal expected. | context: userId=42, email=test@example.com | actual=false expected=true | test.d:15

TAP format:

not ok - result should equal expected.
---
context:
userId: 42
email: test@example.com
actual: false
expected: true
at: test.d:15
...

Combining Both Features

You can use because and withContext together:

result.should.equal(expected)
.because("validation failed for user %s", userId)
.withContext("email", user.email)
.withContext("role", user.role);

Use Cases

Loop Debugging

foreach (i, testCase; testCases) {
auto result = process(testCase.input);
result.should.equal(testCase.expected)
.withContext("index", i)
.withContext("input", testCase.input);
}

Parameterized Tests

static foreach (config; testConfigurations) {
unittest {
auto result = runWith(config);
result.isValid.should.beTrue
.withContext("config", config.name)
.withContext("timeout", config.timeout);
}
}

Multi-Threaded Tests

import std.parallelism;
foreach (task; parallel(tasks)) {
auto result = task.execute();
result.should.equal(task.expected)
.withContext("taskId", task.id)
.withContext("thread", thisThreadId);
}

Complex Object Validation

void validateOrder(Order order) {
order.total.should.beGreaterThan(0)
.withContext("orderId", order.id)
.withContext("customer", order.customerId)
.withContext("items", order.items.length);
order.status.should.equal(Status.pending)
.withContext("orderId", order.id)
.withContext("createdAt", order.createdAt.toISOString);
}

Limits

  • Context is limited to 8 key-value pairs per assertion (defined by MAX_CONTEXT_ENTRIES)
  • Keys and values are converted to strings using std.conv.to!string
  • Context is cleared between assertions
  • If you exceed 8 context entries, a warning is displayed in the output indicating that additional entries were dropped

Next Steps