Open-source project
shouldly/shouldly avatar
shouldly/shouldly

shouldly/shouldly: the assertion library whose main trick is putting the value in the method name

Should testing for .NET—the way assertions should be!

3,411 stars427 forksC#NOASSERTION

At a glance

What is it?
A C# assertion framework with 3,411 stars built around one idea, reading the expression before ShouldBe to build the failure message. Worth reading for the expression-capture mechanism, and for how version 5 is rewriting it.
Who is it for?
Shouldly is small on purpose, and its value is entirely in the failure message, so the interesting question for any adoption is how that message gets built. For the 4.x line it means runtime stack walking, and for the 5.0 preview it means leaning on the C# compiler and adding Native AOT support at the cost of a breaking change list.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The whole idea is a better sentence

Shouldly is described as an assertion framework focused on great error messages when an assertion fails, while staying simple and terse. The README makes the pitch as a before-and-after comparison, starting from the classic NUnit-style assertion and the message it produces on failure, then showing the Shouldly version and the message it produces instead. The difference in that pair is not wording, it is information: the second message names the expression, so `map.IndexOfValue("boo")` appears in the failure rather than only the number 2.

That single capability is what the whole project is built around. The README states it directly: Shouldly uses the code before the ShouldBe statement to report on errors, which makes diagnosing easier. It is worth pausing on how unusual that is. Most assertion libraries in most languages can only report values, so a failure tells you what was expected and what arrived and leaves you to find the call site. An assertion that can see its own left-hand side has to reconstruct source text from a stack trace at runtime, which is the mechanism the 4.x line uses and the thing version 5 is replacing.

The API surface reflects the same minimalism. `ShouldBe` is the representative call, and the repository topics are just `assertion`, `should`, `testing` and `unit`. The project is a C# library of 3,411 stars with 427 forks, not archived, last pushed on 2026-09-23, and it is developed on a `master` branch rather than `main`, which the CI badge in the README also reflects.

Version 5 replaces stack walking with the compiler

The `5.0.0-preview.1` release notes, published on 2026-06-14, describe a major release focused on modernization. The three technical changes are that it leans on the C# compiler instead of runtime stack walking, that it drops several dependencies, and that it adds Native AOT support, along with a refresh of the assertion failure output. The notes are explicit that these bring breaking changes and that the release is a preview inviting reports.

Read against the README, that is a change of mechanism rather than of philosophy. The README's headline feature, reading the code before the statement, is exactly the thing that has to be implemented one way or the other. Runtime stack walking is the version that works on any target framework and needs no compiler cooperation; compiler support is the version that avoids a debugger dependency at runtime, which is a prerequisite for Native AOT and also removes a class of trimming and reflection problems. Trading one for the other is the sort of change that has to be a major version, and the project treats it as one.

A `BREAKING CHANGES.txt` file sits in the repository root, which is the first place to look if you are on 4.x and considering the upgrade. The output format also changes. The preview replaces the old table-based string diff with adaptive markers pointing at the offending character, illustrated in the release notes with an aligned grid of index, expected value, actual value and character codes. Whatever you read in CI logs today, do not assume the same shape after upgrading. The same preview also drops several dependencies, so a transitive package you were relying on may disappear.

A preview on the main package id, and a stable line last shipped in 2025

The release history in the repository metadata is short and tells you where the project actually is. Three releases are listed: `5.0.0-preview.1` from 2026-06-14, `4.3.0` from 2025-01-23, and `4.2.1` from 2023-04-24. So the newest stable version is nearly twenty months older than the newest preview, and the gap between 4.2.1 and 4.3.0 is itself almost two years.

What the stable release notes show is the shape of the maintenance work. The 4.3.0 milestone is mostly dependency and SDK churn attributed to the maintainer: removing obsoletes, bumping the SDK to 8.0.301 and then 8.0.302, removing sourcelink, moving test projects to net8, updating MarkdownSnippets, PublicApiGenerator, Microsoft.NET.Test.Sdk and Microsoft.CodeAnalysis.CSharp, plus a pull request improving flaky tests. There is real feature work too, such as adding ImmutableArray support to `ShouldBeEquivalentTo`. The 4.2.1 notes are thinner and include a determinism-related improvement and an internal dependency removal.

The NuGet badges tell their own small story. The README carries both a download-count badge and a preview-version badge for the same package, which means the preview ships on the main Shouldly package identifier rather than on a separate one. That is convenient for testing the new major version, and it also means a floating version reference or an unpinned restore can land you on a preview build without anyone deciding that for you. Pin the version explicitly if you take the 5.0 preview.

Approval testing is the feature that needs a second package

Beyond value comparison, the feature the README mentions that reaches outside the assertion library is `ShouldMatchApproved`, which compares output against an approved file. It is the standard approach when the thing under test produces text that is too large or too structured to assert one value at a time, and it turns a large diff into a normal review workflow where the approved file is checked in and updated deliberately.

It also has a dependency the README is upfront about. To get a diff of expected against actual rather than a bare path, you need the `Shouldly.DiffEngine` package installed and then configured:

csharp
ShouldMatchConfiguration.ShouldMatchApprovedDefaults.ConfigureDiffEngine();

The install commands themselves are given twice, once per tooling era. The Package Manager Console route is presented as the default, via the Visual Studio Tools menu, and the command line route is introduced as the alternative for .NET Core:

bash
dotnet add package Shouldly

That word choice is the one stale spot in an otherwise current README. .NET Core stopped being the product name years ago, and the 5.0 preview is chasing Native AOT support, which is a modern runtime concern. The `Install-Package` cmdlet still works in the Package Manager Console, so nothing here is broken; the terminology just dates the page. For anything else, the README's closing instruction is to read about the features at docs.shouldly.org.

License metadata reads as nothing while LICENSE.txt exists

The repository metadata reports the license as unrecognized, `NOASSERTION`, while the tree contains `LICENSE.txt` at the root. Those two facts sit side by side, and the difference matters more here than it would for a project nobody is going to vendor. An unrecognized license means GitHub renders no license badge and shows nothing in the sidebar, which for a package with 3,411 stars reads as an absence of information rather than an absence of a license.

The likeliest explanation is a file format GitHub cannot auto-detect. That is common enough to be unremarkable, and many .NET libraries carry a plain text license file. The fix is to add a recognizable `LICENSE` file so the metadata resolves, or to set the license explicitly in the repository settings. Either way, the file being present in the tree is the important fact: an evaluator looking at the repository directly will find it, and one reading the API metadata will not.

Open issues sit at 96, which is high for a library with no open source backlog in the release notes. For a project whose stable line has not shipped since January 2025, that ratio is worth watching, particularly if you are considering the 5.0 preview as a way to get fixes. The counterweight is visible in the 4.3.0 notes: someone filed a pull request specifically to improve flaky tests, which is the kind of unglamorous work that keeps a mature test library honest.

The build tooling in the tree is conventional and unremarkable in a reassuring way. There is a `Shouldly.sln`, a `build.ps1`, an `editorconfig`, a `global.json` for SDK pinning, a `.git-blame-ignore-revs` file so that a history-rewrite commit does not pollute blame, and a `.gitbook.yaml` pointing at the docs site. That last one is the answer to where the real documentation lives: docs.shouldly.org is built from this repository.

What the README deliberately does not cover

At roughly 2,700 characters, the README is an advertisement for one idea rather than a manual. It shows the old way and the new way, states that expression capture is what makes the difference, gives three install commands, and sends you to docs.shouldly.org for features. It does not enumerate the assertion vocabulary, does not show equivalence matching beyond the one word `ShouldBeEquivalentTo` that appears in a release note, and does not explain what the 5.0 compiler-based capture costs in terms of supported language versions or target frameworks.

That last gap is the one to close yourself before upgrading. The trade from runtime stack walking to compiler cooperation has consequences that depend on how your project is built, whether it uses source generators, whether it trims, and whether it targets frameworks the preview still supports. The release notes point at breaking changes and the repository ships a `BREAKING CHANGES.txt`, and neither file is in the README.

So the practical order is simple. If you are starting a new test project, take the 5.0 preview, pin it, and read the breaking changes file. If you have an existing suite on 4.x, the failure-message format change alone is visible in every CI log that fails, so budget for that. And if you are evaluating Shouldly against any other assertion library, the comparison to make is not API surface but how much of the diagnosis you get before opening the source file.

Editorial conclusion

Shouldly is small on purpose, and its value is entirely in the failure message, so the interesting question for any adoption is how that message gets built. For the 4.x line it means runtime stack walking, and for the 5.0 preview it means leaning on the C# compiler and adding Native AOT support at the cost of a breaking change list. Since 4.3.0 in January 2025 is still the newest stable release while the 5.0 preview has been out since June 2026, a new project can reasonably take the preview and should read `BREAKING CHANGES.txt` first, while an existing suite will want to know what changed before moving. Approval testing with `ShouldMatchApproved` is the one feature that pulls in a second package, and the diff output there depends on DiffEngine being installed and configured.

Frequently asked questions

What does Shouldly do differently from a plain assert library?

It reads the expression on the left of the assertion and puts it into the failure message. Instead of a message that says an expected value did not match an actual one, you get a message naming the expression that was evaluated, which saves hunting down the call site.

Is Shouldly 5 stable yet, and what changed in it?

Not yet. The newest release is `5.0.0-preview.1`, published 2026-06-14. It replaces runtime stack walking with compiler support, drops several dependencies, adds Native AOT support and redesigns string diff output with markers instead of a table, all of which are breaking.

How do I install Shouldly in a .NET project?

The README gives the Package Manager Console route inside Visual Studio as `Install-Package Shouldly`, and the command line route as `dotnet add package Shouldly`. A separate `Shouldly.DiffEngine` package is needed if you want `ShouldMatchApproved` to render a diff, followed by a call to configure it.

What license is Shouldly released under?

The repository metadata reports no recognized license, but a `LICENSE.txt` file is present at the root of the tree. So the license is in the repository even though the GitHub license field does not resolve, which usually means the file format is not one GitHub auto-detects.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. shouldly/shouldly on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shouldly-shouldly.svg)](https://hysenlabs.com/projects/shouldly-shouldly)