Fluent Assertions 8.11: Better collection, JSON, string, ValueTask, and custom assertions

Fluent Assertions 8.11 brings practical improvements for .NET developers, including new collection assertions, `System.Text.Json` round-trip testing with `BeJsonSerializable()`, multiline string assertions, and first-class `ValueTask` support. The release also improves custom value-type assertions, error messages, and JSON and reflection assertion behavior.

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

var actual = new[] { 1, 2, 3, 4 };

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

var actual = new[] { 1, 2, 3, 4 };

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

myObject.Should().BeJsonSerializable<MyType>();

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

var 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>
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

Throw() → ThrowAsync()
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

ValueTypeAssertions<TSubject, TAssertions>
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

results.Should().BeEmpty();

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.

Echa un vistazo a la biblioteca de palabras y PDF de Xceed paquete