# Getting Started with Shouldly

> Write cleaner test assertions with better messaging using the Shouldly library

- URL: https://blog.nimblepros.com/blogs/getting-started-with-shouldly/
- Author: [Scott DePouw](https://blog.nimblepros.com/authors/scott/)
- Published: 2025-01-20
- Categories: [Software Development](https://blog.nimblepros.com/categories/software-development/)
- Publisher: [NimblePros](https://blog.nimblepros.com/)

---

The out-of-the-box method of performing assertions in unit tests works well, but there are better, more fluid ways of writing assertions. Rather than writing something akin to `Assert.That(something, is.EqualTo(somethingElse))`, one could write `something.ShouldBe(somethingElse)`. Less boilerplate, easier to read!

There are a few libraries (in the form of NuGet packages for C#/.NET) that help reach this goal. Recently, I discovered [Shouldly](https://docs.shouldly.org/). Let's try writing some tests with it together.

## Source Code for Example Project

A full version of the code examples below is available [here, in my Shouldly Sandbox project](https://github.com/sdepouw/ShouldlySandbox). It is in .NET 9 using XUnit.

## Shouldly Error Messages

When tests fail, it's important to have clarity in why they did. Shouldly opts for easy-to-read messages. Here's an example of our first test failing (screenshot taken in JetBrains Rider). In this example, we have a `BattleManager` class. One of the methods is `CalculateDamage()`. Our test asserts that the expected damage with the provided strength should be `1030`, but we get `100` back instead. Thus our tests fails. Here's how Shouldly reports the failure:

![Screenshot: Failing Test Example](screenshot-test-failure-example.png)

> Shouldly clearly defines the expectation, the actual result, the stack trace, and more.

Most of the assertion methods accept an optional `string? customMessage` parameter, which will show up in failing tests as "Additional Info" should you want to provide any.

## Writing Tests with Shouldly

Let's go over some examples of writing more unit tests against our `BattleManager` class, using Shouldly's assertions to verify behavior.

### The Great Migration

The assertions library I'm most familiar with is FluentAssertions. The bulk of assertions in that library are written as `foo.Should().Be(bar)`. For others familiar with that library, you'll find a transition to Shouldly very simple: Merge the methods!

### ShouldBe()

For most Shouldly assertions, simply merging the `Should()` and `Be()` is all you have to do. The previous example becomes `foo.ShouldBe(bar)`.

Here's a full example, which can be found in [my Shouldly Sandbox project](https://github.com/sdepouw/ShouldlySandbox). In it, we correct the assertion of `CalculateDamage()`, as it is indeed supposed to return `100` in this scenario.

```csharp
// Class under test
namespace ShouldlySandbox;

public class BattleManager
{
  public int CalculateDamage(int strength) => strength + 42;
}

// Unit test file
using Shouldly;
using Xunit;

namespace ShouldlySandbox;

public class BattleManagerTests
{
  private readonly BattleManager _battleManager = new();
  
  [Fact]
  public void CalculatesDamageAs100Given58Strength()
  {
    const int expectedCalculatedDamage = 100;
    const int strength = 58;
    
    int calculatedDamage = _battleManager.CalculateDamage(strength);
    
    // Our first test using Shouldly!
    calculatedDamage.ShouldBe(expectedCalculatedDamage);
  }
}
```

You can find a [mostly-accurate list of translations from FluentAssertions to Shouldly here](https://github.com/shouldly/shouldly/issues/1034#issuecomment-2594180707). The majority of conversions do this same merge into `ShouldBe()`. Convenient!

Let's go over a few other common assertion scenarios and see the cases where Shouldly differs beyond the merging of the `Should()` and `Be()` methods.

### Counting

To assert a collection has the appropriate number of elements, assert against the `Count` or `Length` property.

```csharp
[Fact]
public void TheShopHasFiveItemsForSale()
{
  const int expectedNumberOfItems = 5;
  
  List<decimal> shopPrices = _battleManager.GetShopPrices();
  
  shopPrices.Count.ShouldBe(expectedNumberOfItems);
}
```

### Exceptions

Specify the type of exception thrown, and call `ShouldThrow<TExceptionType>()`.

```csharp
[Fact]
public void DamageCalculationRejectsNegativeStrength()
{
  const int invalidStrength = -1;
  
  Action calculateDamageCall = () => _battleManager.CalculateDamage(invalidStrength);
  
  calculateDamageCall.ShouldThrow<InvalidStrengthException>();
}
```

`ShouldThrow` returns the thrown Exception, which you can further write assertions against. We can augment our above test to assert the bad strength value is included in the exception message.

```csharp
InvalidStrengthException thrownException = calculateDamageCall.ShouldThrow<InvalidStrengthException>();
thrownException.Message.ShouldContain(invalidStrength.ToString());
```

When an exception *shouldn't* be thrown, use `ShouldNotThrow()`:

```csharp
[Fact]
public void DamageCalculationAccepts0Strength()
{
  const int acceptableStrength = 0;
  
  Action calculateDamageCall = () => _battleManager.CalculateDamage(acceptableStrength);

  calculateDamageCall.ShouldNotThrow();
}
```

For asynchronous calls, `await` the assertion and call the relevant Async methods. Remember to change your XUnit tests from `public void` to `public async Task`.

```csharp
[Fact]
public async Task ExplodeWillNeverBeImplemented()
{
  Func<Task> explodeCall = () => _battleManager.Explode();

  await explodeCall.ShouldThrowAsync<NotImplementedException>();
}

[Fact]
public async Task ToBattleShouldNotExplode()
{
  Func<Task> toBattleCall = () => _battleManager.ToBattle();

  await toBattleCall.ShouldNotThrowAsync();
}
```

## Multiple Assertions

Most of the time, when we have multiple assertions in a unit test, we want to see all of them run regardless of whether the first one fails. Otherwise, a test would have to be run multiple times to progress to the next failing state.

In Shouldly, this is accomplished by using the [SatisfyAllConditions](https://docs.shouldly.org/documentation/satisfyallconditions) method.

```csharp
[Fact]
public void ShopPricesAreNotTooExpensive()
{
  List<decimal> shopPrices = _battleManager.GetShopPrices();
  
  shopPrices.ShouldSatisfyAllConditions(
    () => shopPrices.First().ShouldBeLessThan(3m),
    () => shopPrices.Sum().ShouldBeLessThan(5000m),
    () => shopPrices[1].ShouldBeLessThan(50m),
    () => shopPrices[1].ShouldBeGreaterThan(10m)
  );
}
```

This is what one would use to assert multiple conditions on the same item as well as multiple. Note how we do this against `shopPrices[1]`, asserting that its price range is `($10, $50)`.

## Collections

There are a couple scenarios to consider when asserting that a collection of items is expected:

- The order of items in the collection matters
- The order of items in the collection does not matter

For the former, simply use `ShouldBe()`.

```csharp
[Fact]
public void ShopPricesReturnCorrectAmounts()
{
  List<decimal> expectedShopPrices = [2.99m, 45m, 1000m, 65m, 457.22m];
  
  List<decimal> shopPrices = _battleManager.GetShopPrices();
  
  // The order of items in the list matters!
  shopPrices.ShouldBe(expectedShopPrices);
}
```

For the latter, within `ShouldBe()` specify `ignoreOrder: true`.

```csharp
[Fact]
public void ShopPricesReturnCorrectAmountsRegardlessOfOrder()
{
  List<decimal> expectedShopPrices = [457.22m, 1000m, 2.99m, 45m, 65m];
  
  List<decimal> shopPrices = _battleManager.GetShopPrices();
  
  // The order of items in the list does not matter!
  shopPrices.ShouldBe(expectedShopPrices, ignoreOrder: true);
}
```

## And Many More

We're just getting started with Shouldly here! The above examples (along with the Sandbox project accompanying this post) should be more than enough to get you up and running with this library. Happy testing!

## References

- [My Shouldly Sandbox Examples](https://github.com/sdepouw/ShouldlySandbox/)
- [Shouldly on GitHub](https://github.com/shouldly/shouldly/)
- [Shouldly Documentation](https://docs.shouldly.org/)
- [Migrating from FluentAssertions to Shouldly](https://github.com/shouldly/shouldly/issues/1034)
- [Shouldly Maintainer Q&A](https://github.com/shouldly/shouldly/issues/1034#issuecomment-2598457336)
