Waystone.Monads.Shouldly 7.3.0

dotnet add package Waystone.Monads.Shouldly --version 7.3.0
                    
NuGet\Install-Package Waystone.Monads.Shouldly -Version 7.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Waystone.Monads.Shouldly" Version="7.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Waystone.Monads.Shouldly" Version="7.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Waystone.Monads.Shouldly" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Waystone.Monads.Shouldly --version 7.3.0
                    
#r "nuget: Waystone.Monads.Shouldly, 7.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Waystone.Monads.Shouldly@7.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Waystone.Monads.Shouldly&version=7.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Waystone.Monads.Shouldly&version=7.3.0
                    
Install as a Cake Tool

Waystone.Monads.Shouldly

Shouldly assertions for Option<T> and Result<TOk, TErr>.

Why

Checking a monad through its booleans throws the monad away before the assertion runs, so a failure can only report the boolean:

option.IsSome.ShouldBeTrue();

option.IsSome
    should be
True
    but was
False

The same assertion here reports what the option was:

option.ShouldBeSome();

option
    should be Some
    but was
None

On a Result the difference is larger, because the error a failing test did not expect is usually the whole explanation:

result.ShouldBeOk();

result
    should be Ok
    but was
Err("connection refused")

Usage

The extensions live in the Shouldly namespace, so a test file that already has using Shouldly; needs no new import.

The namespace is Shouldly rather than Waystone.Monads.Shouldly for a second reason, and it is not a matter of taste: a nested Waystone.Monads.Shouldly namespace shadows the global Shouldly for every file declared in namespace Waystone.Monads, because C# resolves a using outward from the enclosing namespace and stops at the first match. Under that layout using Shouldly; in this repository's own tests would bind to the nested namespace and every plain Shouldly assertion would stop compiling. Renaming the namespace to match the package id looks like a tidy-up and silently breaks the callers most likely to use it.

option.ShouldBeSome();
option.ShouldBeNone();
option.ShouldBeSomeValue(3);

result.ShouldBeOk();
result.ShouldBeErr();
result.ShouldBeOkValue(3);
result.ShouldBeErrValue("failed");

Every assertion takes an optional customMessage, printed under an Additional Info heading rather than replacing the generated message.

Returning the value

Every assertion but ShouldBeNone hands back what it unwrapped, so a check on the state and a check on the contents are one statement instead of two:

result.ShouldBeOk().Name.ShouldBe("waystone");

Awaited receivers

Each assertion has an *Async form on Task<Option<T>>, ValueTask<Option<T>> and the two Result equivalents, so an assertion on an async chain does not need to be wrapped in parentheses to await it:

// before
(await repository.FindAsync(id)).ShouldBeSomeValue(expected);

// after
await repository.FindAsync(id).ShouldBeSomeValueAsync(expected);

They return ValueTask, matching the rest of Waystone.Monads, and they forward the caller's expression to the synchronous assertion — so the message is identical apart from naming your receiver.

The Async suffix is deliberate. An overload sharing the synchronous name would read identically to the correct synchronous call when the await is missing — and a discarded ValueTask means the assertion never runs, so the test passes without checking anything. The suffix makes that mistake visible.

How the message is built

Following Shouldly's extension guide: the classes carry [ShouldlyMethods], [DebuggerStepThrough] and [EditorBrowsable(EditorBrowsableState.Never)], every assertion takes an optional customMessage, and the receiver's source text arrives through [CallerArgumentExpression] so the failure names your expression rather than a parameter.

Two deliberate departures from that page, both forced by the Shouldly 4.3.0 this package builds against:

  • The message text is built here rather than by ActualShouldlyMessage or ExpectedActualShouldlyMessage. In 4.3.0 the first prints a literal null where a state assertion has no expected value, and the second ends but was not without printing the actual. Neither can render but was Err("failed"), which is the entire point of the package. The layout emitted here matches theirs.
  • [ShouldlyMethods] is kept. The guide calls it unnecessary for targets with [CallerArgumentExpression], but retained for netstandard2.0 — which is this package's target framework.

Option<T> and Result<TOk, TErr> are records, so Shouldly's own object formatter renders a Some as Some { IsSome = True, IsNone = False } — without its value. That is why the description is formatted here as Some(3) and Err("failed").

Comparison of values

ShouldBeSomeValue, ShouldBeOkValue and ShouldBeErrValue report the wrong state themselves, then hand a wrong value to Shouldly's own comparison. That keeps its diff on strings and collections, which is better than anything this package could restate.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
7.3.0 39 9/13/2026
7.2.0 80 9/9/2026
7.1.0 93 9/4/2026
7.1.0-beta.2 51 9/3/2026
7.1.0-beta.1 67 9/2/2026
7.0.1 91 8/30/2026
7.0.0 93 8/30/2026
7.0.0-beta.3 56 8/29/2026
7.0.0-beta.2 59 8/27/2026
7.0.0-beta.1 61 8/27/2026