home / skills / stuartf303 / sorcha / fluent-assertions

fluent-assertions skill

/.claude/skills/fluent-assertions

This skill helps you write clear, fluent test assertions using FluentAssertions to improve test readability and reliability.

npx playbooks add skill stuartf303/sorcha --skill fluent-assertions

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
3.7 KB
---
name: fluent-assertions
description: |
  Creates readable test assertions with FluentAssertions library.
  Use when: writing unit tests, integration tests, verifying object state, testing exceptions, asserting collections
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, mcp__context7__resolve-library-id, mcp__context7__query-docs
---

# FluentAssertions Skill

FluentAssertions provides a fluent interface for writing test assertions in C#. The Sorcha codebase uses it across 1,100+ tests with xUnit. All assertions follow the `subject.Should().BeX()` pattern.

## Quick Start

### Basic Value Assertions

```csharp
// Strings
wallet.Address.Should().NotBeNullOrEmpty();
blueprint.Title.Should().Be("Purchase Order");
error.Message.Should().Contain("validation");

// Numbers
blueprint.Version.Should().Be(5);
result.Count.Should().BeGreaterThan(0).And.BeLessThan(100);

// Booleans
result.IsSuccess.Should().BeTrue();
validation.IsValid.Should().BeFalse();
```

### Exception Testing

```csharp
// Sync exception testing
builder.Invoking(b => b.Build())
    .Should().Throw<InvalidOperationException>()
    .WithMessage("*title*");  // Wildcard matching

// Async exception testing
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
    await _walletManager.CreateWalletAsync("Test", null!, "user1", "tenant1"));
```

### Collection Assertions

```csharp
blueprint.Participants.Should().HaveCount(2);
blueprint.Actions.Should().NotBeEmpty();
events.Should().ContainSingle(e => e is WalletCreatedEvent);
wallets.Should().AllSatisfy(w => w.Owner.Should().Be(owner));
docket.TransactionIds.Should().ContainInOrder("tx1", "tx2", "tx3");
```

## Key Concepts

| Concept | Usage | Example |
|---------|-------|---------|
| Should() | Entry point for all assertions | `value.Should().Be(expected)` |
| And | Chain multiple assertions | `.NotBeNull().And.HaveCount(2)` |
| Which/WhoseValue | Access nested values | `.ContainKey("x").WhoseValue.Should().Be("y")` |
| Invoking | Test sync exceptions | `obj.Invoking(x => x.Method())` |
| Awaiting | Test async exceptions | `obj.Awaiting(x => x.MethodAsync())` |

## Common Patterns

### Object Property Assertions

```csharp
wallet.Should().NotBeNull();
wallet.Name.Should().Be("Test Wallet");
wallet.Status.Should().Be(WalletStatus.Active);
wallet.Metadata.Should().ContainKey("environment").WhoseValue.Should().Be("production");
```

### Time-Based Assertions

```csharp
docket.TimeStamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
blueprint.UpdatedAt.Should().BeOnOrAfter(beforeBuild).And.BeOnOrBefore(afterBuild);
```

### Validation Result Assertions

```csharp
result.Validation.IsValid.Should().BeTrue();
result.Errors.Should().BeEmpty();

// Or for failures
result.Success.Should().BeFalse();
result.Validation.Errors.Should().NotBeEmpty();
result.Errors.Should().Contain(e => e.Contains("validation"));
```

## See Also

- [patterns](references/patterns.md)
- [workflows](references/workflows.md)

## Related Skills

- See the **xunit** skill for test framework patterns
- See the **moq** skill for mocking dependencies in tests

## Documentation Resources

> Fetch latest FluentAssertions documentation with Context7.

**How to use Context7:**
1. Use `mcp__context7__resolve-library-id` to search for "fluent-assertions"
2. **Prefer website documentation** (IDs starting with `/websites/`) over source code repositories when available
3. Query with `mcp__context7__query-docs` using the resolved library ID

**Library ID:** `/fluentassertions/fluentassertions`

**Recommended Queries:**
- "FluentAssertions basic assertions Should Be"
- "FluentAssertions collection assertions HaveCount Contain"
- "FluentAssertions exception testing ThrowAsync"
- "FluentAssertions object equivalence BeEquivalentTo"

Overview

This skill creates readable, expressive test assertions using the FluentAssertions library for C#. It helps you write clear unit and integration tests that read like specifications, improving maintainability and diagnostics. The API follows the subject.Should().BeX() pattern and supports values, collections, exceptions, and time-based checks.

How this skill works

The skill generates FluentAssertions-style assertions that inspect object state, collections, exceptions, and temporal conditions. It produces chained assertions (And, Which, WhoseValue) and uses Invoking/Awaiting for synchronous and asynchronous exception checks. Outputs are concise one-liners or short chains that integrate with xUnit or other test frameworks.

When to use it

  • Writing unit tests to verify single-value results and object properties
  • Asserting collection contents, counts, ordering, or per-item conditions
  • Testing that methods throw expected exceptions (sync and async)
  • Verifying validation results or error messages
  • Asserting time-related properties like timestamps or durations

Best practices

  • Prefer readable chains: subject.Should().NotBeNull().And.HaveCount(2) rather than many separate assertions
  • Use BeEquivalentTo for deep object comparisons where order or exact runtime types don’t matter
  • Use Invoking(...).Should().Throw<ExceptionType>() for synchronous exceptions and Awaiting/ThrowAsync for async methods
  • Assert collection expectations with HaveCount, ContainSingle, ContainInOrder, or AllSatisfy for per-item checks
  • Include message or property checks where failures need clear diagnostics (WithMessage, WhoseValue)

Example use cases

  • Assert a DTO returned from a service: result.Should().NotBeNull().And.BeEquivalentTo(expected)
  • Verify a collection of events: events.Should().ContainSingle(e => e is WalletCreatedEvent) and wallets.Should().AllSatisfy(w => w.Owner.Should().Be(owner))
  • Test exception scenarios: builder.Invoking(b => b.Build()).Should().Throw<InvalidOperationException>().WithMessage("*title*")
  • Check validation outcomes: result.Validation.IsValid.Should().BeTrue() and result.Errors.Should().BeEmpty()
  • Assert timestamps: docket.TimeStamp.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1))

FAQ

When should I use BeEquivalentTo vs Be?

Use Be for primitive or exact-reference equality. Use BeEquivalentTo to compare object graphs by value when property order or concrete runtime types may differ.

How do I test async methods that throw?

Use the Awaiting API or your test framework’s async assertion helpers. For FluentAssertions: await subject.Awaiting(s => s.MethodAsync()).Should().ThrowAsync<ExceptionType>().