Fluent Assertions 8.11 expands the library with new assertions for collections, JSON serialization, multiline strings, and ValueTask, while also making custom value-type assertions easier to build.
Fluent Assertions 8.11 expands the library with new assertions for collections, JSON serialization, multiline strings, and ValueTask, while also making custom value-type assertions easier to build.
The release is particularly useful if your test suite works heavily with System.Text.Json, asynchronous APIs, reflection, or collections. It also includes several fixes designed to produce clearer assertion failures instead of unexpected exceptions.
Here is what .NET developers should know about Fluent Assertions 8.11.
New collection assertions for subset and superset testing
Fluent Assertions 8.11 adds three collection assertions:
- BeSupersetOf: Verifies that every item in the expected collection exists in the actual collection.
- BeProperSubsetOf: Verifies that the subject is contained within another set while remaining strictly smaller.
- BeProperSupersetOf: Verifies that the subject contains another set and is not simply equal to it.
These assertions make set relationships easier to express directly in tests.
Por ejemplo:
Using BeSupersetOf
actual.Should().BeSupersetOf(new[] { 2, 3 });
The assertion communicates the intent immediately: every item in the expected collection should exist in the actual collection.
A proper superset adds another condition. The collections cannot simply represent the same set.
Using BeProperSupersetOf
actual.Should().BeProperSupersetOf(new[] { 1, 2, 3 });
Similarly, BeProperSubsetOf can be used when the subject must be contained within another collection while still being strictly smaller as a set.
Empty subsets now behave naturally
There is an important edge case in the new behavior.
Empty subset behavior
An empty expected subset is considered valid for BeSupersetOf y BeProperSupersetOf rather than causing an exception.
That makes the assertions behave more naturally from a set-theory perspective and removes the need for special handling around empty expected collections.
Test System.Text.Json round-tripping with BeJsonSerializable()
One of the most useful additions in 8.11 for modern .NET applications is BeJsonSerializable<T>().
JSON round-trip assertion
Available on .NET 6 and later, the assertion verifies System.Text.Json serialization round-tripping.
In other words, you can verify that an object can be serialized to JSON, deserialized again, and still produce an equivalent object without manually building the entire serialization round-trip inside the test.
This is particularly useful for DTOs, API contracts, configuration objects, persisted application state, and other models that regularly cross serialization boundaries.
Customize serialization and equivalency
The new assertion also provides overloads for equivalency configuration and JsonSerializerOptions.
That matters because real applications rarely rely exclusively on the default serializer configuration.
- Naming policies: Match the JSON property naming conventions used by the application.
- Custom converters: Test application-specific serialization behavior.
- Enum serialization: Verify enums using the configured representation.
- Reference handling: Apply the same reference behavior used at runtime.
- Case sensitivity: Reflect the application's serializer settings.
- Ignore conditions: Test using the same property exclusion rules as production code.
Being able to supply JsonSerializerOptions allows the assertion to more closely reflect the serialization behavior used by the application itself.
The result is a test that describes its purpose clearly without hiding that purpose behind serialization boilerplate.
New assertions for multiline strings
Testing multiline output has also become easier.
StringAssertions now includes several dedicated line-oriented assertions.
- HaveLineCount()
- NotHaveLineCount()
- ContainLine()
- NotContainLine()
These are useful for testing generated reports, logs, command-line output, templates, formatted messages, exported text, and other multiline content.
Testing multiline output
Build started
Compilation successful
Build completed
""";
output.Should().HaveLineCount(3);
output.Should().ContainLine("Compilation successful");
Previously, developers might split the string manually before asserting against the resulting collection. Now the intent can remain at the string assertion level.
Cross-platform line ending normalization
The assertions normalize \r\n, \ny \r.
Why line-ending normalization matters
Tests dealing with generated text can otherwise become dependent on the line-ending convention of the operating system or source data. Normalizing those endings makes line-oriented assertions less fragile across Windows and other environments.
ContainLine also returns an AndWhichConstraint, exposing the matching line so additional assertions can be chained against it.
ValueTask now receives first-class assertion support
Fluent Assertions 8.11 adds Should() overloads for Func<ValueTask> y Func<ValueTask<T>>.
Supported ValueTask delegates
Func<ValueTask<T>>
That brings ValueTask-returning delegates in line with the assertion experience already available for Tarea-returning delegates.
This is useful for libraries and performance-sensitive .NET code where ValueTask is exposed intentionally to reduce allocations in scenarios where operations may complete synchronously.
A ValueTask API no longer needs to feel like a second-class citizen in the test suite. There is, however, an important migration consideration.
Breaking change: existing ValueTask assertions may need updating
Before 8.11, Func<ValueTask> y Func<ValueTask<T>> could bind to the synchronous Should<T>(Func<T>) overload.
That meant Fluent Assertions treated the returned ValueTask as an ordinary return value.
With the new dedicated overloads, these delegates now bind to asynchronous assertions instead.
ValueTask migration changes
ThrowExactly() → ThrowExactlyAsync()
NotThrow() → NotThrowAsync()
NotThrowAfter() → NotThrowAfterAsync()
There is another behavioral difference worth checking during an upgrade: Subject now exposes a task-based adapter rather than the original delegate.
For teams with APIs that make extensive use of ValueTask, this should be one of the first areas reviewed when moving to 8.11.
Easier custom assertions for value types
Fluent Assertions 8.11 also introduces two public base classes designed to reduce the work required when creating custom assertions for value types.
New value-type assertion base classes
NullableValueTypeAssertions<TSubject, TAssertions>
Both derive from ValueTypeAssertionsBase<TSubject, TSubjectResult, TAssertions>.
For non-nullable subjects, the new base class provides assertions such as Be(), NotBe()y Match().
Nullable value-type assertions additionally receive BeNull(), NotBeNull(), HaveValue(), NotHaveValue()y Match(), with support for nullable matching.
This is particularly valuable for teams building domain-specific assertion libraries around custom structs and other value types. Instead of recreating common assertion behavior, extension authors can build on a public foundation supplied by Fluent Assertions.
Better reflection filtering with ThatSatisfy
MethodInfoSelector y PropertyInfoSelector now support ThatSatisfy.
This gives developers another way to filter reflected methods and properties according to a custom condition.
It is a relatively specialized addition compared with the new collection or JSON assertions, but it can make reflection-heavy architectural tests and custom assertion scenarios more expressive.
For teams using Fluent Assertions to validate API conventions, property structures, or method characteristics, ThatSatisfy provides additional control over which members are selected.
BeEmpty now provides more useful failure information
The release also improves an existing collection assertion.
En BeEmpty fails for an IEnumerable<T>, Fluent Assertions now reports the first 10 items rather than showing only the first item.
BeEmpty example
Si results unexpectedly contains several objects, seeing more of the collection gives developers considerably more context about what went wrong.
This does not change the assertion itself, but it can shorten the debugging cycle when a test fails.
More reliable JsonNode property assertions
Fluent Assertions 8.11 includes two fixes around JsonNodeAssertions.
First, HaveProperty y NotHaveProperty now correctly distinguish between a property that does not exist and a property that exists with an explicit null value.
Those are semantically different JSON states:
Missing property
Property with an explicit null value
"name": null
}
Tests validating API responses or JSON documents often need to distinguish between the two. The release now handles that distinction correctly.
Another fix prevents JsonNodeAssertions.HaveProperty from throwing an InvalidOperationException when it is called on a subject that is not a JsonObject while inside an AssertionScope.
Instead, the assertion infrastructure can produce the intended failure behavior.
Cleaner failures for reflection assertions
Several reflection-related assertions previously had another issue when used inside an AssertionScope.
Affected PropertyInfoAssertions included:
- BeVirtual()
- NotBeVirtual()
- BeWritable()
- NotBeWritable()
- BeReadable()
- NotBeReadable()
- Return()
- NotReturn()
TypeAssertions.HaveProperty y HaveIndexer were affected as well.
Under certain failure conditions, these assertions could produce an intermediate NullReferenceException before Fluent Assertions had the opportunity to generate the proper assertion failure.
Version 8.11 fixes this behavior.
Why cleaner failures matter
An assertion library should explain why an expectation failed. An unrelated NullReferenceException can send developers debugging the assertion machinery instead of the code under test.
Why Fluent Assertions 8.11 matters
Fluent Assertions 8.11 is not centered around one major headline feature. Instead, it improves several everyday testing workflows across modern .NET applications.
- Collection testing: Set-oriented assertions with BeSupersetOf, BeProperSubsetOf, and BeProperSupersetOf.
- JSON testing: System.Text.Json round-trip testing with BeJsonSerializable<T>().
- Multiline strings: Dedicated assertions for line counts and matching lines.
- Asynchronous APIs: Native ValueTask delegate support.
- Custom assertions: Public base classes for value-type assertions.
- Better diagnostics: More useful BeEmpty failure output.
- Cleaner failures: More predictable JSON and reflection assertion behavior.
For many developers, BeJsonSerializable<T>() and the new line assertions will eliminate small pieces of repetitive test code immediately. Library authors may get more value from the new value-type assertion base classes and ValueTask support.
The overall direction is consistent with what makes fluent testing APIs useful: tests should communicate the behavior being verified without forcing developers to write unnecessary setup code around the assertion itself.
What to check before upgrading
The main migration concern is ValueTask.
- Find ValueTask tests: Search for assertions against Func<ValueTask> and Func<ValueTask<T>>.
- Review synchronous exception assertions: Look for Throw, ThrowExactly, NotThrow, and NotThrowAfter.
- Move to async equivalents: Update affected tests to ThrowAsync, ThrowExactlyAsync, NotThrowAsync, and NotThrowAfterAsync.
- Check Subject usage: Subject now exposes a task-based adapter rather than the original delegate.
For projects that do not currently assert against ValueTask delegates, the release primarily provides additive APIs and fixes that can be adopted incrementally.
Final thoughts
Fluent Assertions 8.11 gives .NET developers more precise ways to describe what their tests expect while reducing custom assertion plumbing.
The new JSON round-trip assertion is especially relevant for modern .NET applications built around System.Text.Json, while the collection and multiline string APIs make common expectations easier to express directly. First-class ValueTask support also brings the assertion model closer to the asynchronous APIs developers are building today.
For teams upgrading, review existing ValueTask assertions for the breaking overload change. Beyond that, the new APIs are worth exploring wherever your test suite currently contains manual collection comparisons, string splitting, JSON round-tripping, or custom value-type assertion infrastructure.
Building production .NET applications?
Explore Xceed’s .NET components and tools designed for performance, maintainability, and long-term development.
Fluent Assertions 8.11 FAQ
What is new in Fluent Assertions 8.11?
Fluent Assertions 8.11 adds new subset and superset collection assertions, BeJsonSerializable<T>(), multiline string assertions, first-class ValueTask support, public base classes for custom value-type assertions, reflection filtering improvements, and several assertion failure fixes.
What does BeJsonSerializable<T>() do?
BeJsonSerializable<T>() verifies a System.Text.Json serialization round-trip. It checks that an object can be serialized, deserialized, and produce an equivalent object. The assertion is available on .NET 6 and later and supports serializer and equivalency configuration.
Does Fluent Assertions 8.11 contain a breaking change?
Yes. Tests using Func<ValueTask> or Func<ValueTask<T>> may be affected because these delegates now bind to dedicated asynchronous assertion overloads. Synchronous exception assertions such as Throw() and NotThrow() may need to be replaced with their asynchronous equivalents.
Which multiline string assertions were added?
Fluent Assertions 8.11 adds HaveLineCount(), NotHaveLineCount(), ContainLine(), and NotContainLine(). The assertions normalize common Windows and Unix-style line endings to make cross-platform tests less fragile.
Which collection assertions are new in Fluent Assertions 8.11?
The release adds BeSupersetOf, BeProperSubsetOf, and BeProperSupersetOf, allowing set relationships to be expressed directly in collection tests.
What should I check before upgrading to Fluent Assertions 8.11?
Review tests that use ValueTask-returning delegates, particularly those using Throw, ThrowExactly, NotThrow, or NotThrowAfter. Those tests may need to use the corresponding asynchronous assertion methods after upgrading.