home / skills / stuartf303 / sorcha / xunit

This skill helps you create and validate xUnit tests across multiple projects using FluentAssertions and Moq for reliable C# test coverage.

npx playbooks add skill stuartf303/sorcha --skill xunit

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

Files (3)
SKILL.md
4.1 KB
---
name: xunit
description: |
  Writes unit tests with xUnit framework across 30 test projects.
  Use when: writing new tests, adding test coverage, creating integration tests, setting up test fixtures, or debugging test failures.
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, mcp__context7__resolve-library-id, mcp__context7__query-docs
---

# xUnit Skill

xUnit 2.9.3 is the testing framework for all 30 test projects in Sorcha. Tests use **FluentAssertions** for readable assertions and **Moq** for mocking. All tests follow strict `MethodName_Scenario_ExpectedBehavior` naming.

## Quick Start

### Unit Test Structure

```csharp
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 Sorcha Contributors

public class WalletManagerTests
{
    private readonly Mock<IRepository<Wallet>> _mockRepository;
    private readonly WalletManager _sut;

    public WalletManagerTests()
    {
        _mockRepository = new Mock<IRepository<Wallet>>();
        _sut = new WalletManager(_mockRepository.Object);
    }

    [Fact]
    public async Task CreateAsync_ValidWallet_ReturnsSuccess()
    {
        // Arrange
        var wallet = new Wallet { Name = "Test" };
        _mockRepository.Setup(r => r.AddAsync(wallet)).ReturnsAsync(wallet);

        // Act
        var result = await _sut.CreateAsync(wallet);

        // Assert
        result.IsSuccess.Should().BeTrue();
        result.Value.Should().Be(wallet);
    }
}
```

### Theory with InlineData

```csharp
[Theory]
[InlineData(12)]
[InlineData(15)]
[InlineData(18)]
[InlineData(21)]
[InlineData(24)]
public void GenerateMnemonic_ValidWordCount_ReturnsCorrectLength(int wordCount)
{
    var result = _keyManager.GenerateMnemonic(wordCount);

    result.IsSuccess.Should().BeTrue();
    result.Value!.Split(' ').Should().HaveCount(wordCount);
}
```

## Key Concepts

| Concept | Usage | Example |
|---------|-------|---------|
| `[Fact]` | Single test case | `[Fact] public void Method_Test() {}` |
| `[Theory]` | Parameterized tests | `[Theory] [InlineData(1)] public void Method(int x) {}` |
| `IClassFixture<T>` | Per-class shared state | `class Tests : IClassFixture<DbFixture>` |
| `ICollectionFixture<T>` | Cross-class shared state | `[Collection("Db")] class Tests` |
| `IAsyncLifetime` | Async setup/teardown | `Task InitializeAsync()`, `Task DisposeAsync()` |

## Common Patterns

### Exception Testing

```csharp
[Fact]
public void Constructor_NullRepository_ThrowsArgumentNullException()
{
    var act = () => new WalletManager(null!);

    act.Should().Throw<ArgumentNullException>()
        .WithParameterName("repository");
}

[Fact]
public async Task ProcessAsync_InvalidData_ThrowsWithMessage()
{
    var exception = await Assert.ThrowsAsync<InvalidOperationException>(
        () => _processor.ProcessAsync(invalidContext));

    exception.Message.Should().Contain("validation failed");
}
```

### Async Test Pattern

```csharp
[Fact]
public async Task ExecuteAsync_ValidBlueprint_CompletesSuccessfully()
{
    // Arrange
    var blueprint = CreateTestBlueprint();

    // Act
    var result = await _engine.ExecuteAsync(blueprint);

    // Assert
    result.Success.Should().BeTrue();
    result.ProcessedData.Should().ContainKey("output");
}
```

## See Also

- [patterns](references/patterns.md) - Test patterns and anti-patterns
- [workflows](references/workflows.md) - Test workflows and fixtures

## Related Skills

- See the **fluent-assertions** skill for assertion patterns
- See the **moq** skill for mocking dependencies
- See the **entity-framework** skill for database testing with InMemory provider
- See the **postgresql** skill for Testcontainers integration tests

## Documentation Resources

> Fetch latest xUnit documentation with Context7.

**How to use Context7:**
1. Use `mcp__context7__resolve-library-id` to search for "xunit"
2. Query with `mcp__context7__query-docs` using the resolved library ID

**Library ID:** `/xunit/xunit.net` _(875 code snippets, High reputation)_

**Recommended Queries:**
- "xUnit Theory InlineData patterns"
- "IClassFixture ICollectionFixture shared context"
- "IAsyncLifetime async setup teardown"
- "xUnit parallel test execution configuration"

Overview

This skill writes and organizes unit and integration tests using the xUnit framework across 30 C# test projects. It provides patterns, naming conventions, and common setups to produce consistent, readable, and maintainable tests for a decentralised Secure Data Governance and Disclosure System. The skill emphasizes FluentAssertions for assertions and Moq for mocking to keep tests expressive and focused.

How this skill works

The skill generates test code that follows the MethodName_Scenario_ExpectedBehavior naming convention and uses [Fact] for single cases and [Theory] with InlineData for parameterized cases. It scaffolds common test fixtures (IClassFixture, ICollectionFixture) and async lifecycle hooks (IAsyncLifetime) and applies common patterns for exception testing, async tests, and mocking repository or service dependencies. Tests use FluentAssertions for clear assertions and Moq setups to simulate dependencies.

When to use it

  • Writing new unit tests for services, managers, or repositories
  • Adding test coverage for bug fixes or new features
  • Creating integration tests with shared fixtures and Testcontainers
  • Setting up reusable test fixtures and async setup/teardown
  • Debugging failing tests and reproducing scenarios across projects
  • Standardizing test structure and naming across multiple test projects

Best practices

  • Follow MethodName_Scenario_ExpectedBehavior for each test method name
  • Prefer FluentAssertions for readable, intent-revealing assertions
  • Use Moq to isolate dependencies and verify interactions, not implementation details
  • Use IClassFixture/ICollectionFixture for shared expensive resources to speed execution
  • Use IAsyncLifetime for async setup/teardown when interacting with external resources
  • Keep tests small: Arrange, Act, Assert with one logical assertion per test

Example use cases

  • Create unit tests for WalletManager methods that interact with IRepository<Wallet> using Moq setups
  • Add Theory tests for methods that vary by input (e.g., mnemonic generation word counts)
  • Write integration tests with a shared database fixture using ICollectionFixture and InMemory or Testcontainers providers
  • Implement async lifecycle in fixtures to create and tear down external resources safely
  • Write exception tests that assert thrown types and message contents with FluentAssertions

FAQ

Should I use [Fact] or [Theory]?

Use [Fact] for single-case assertions and [Theory] with InlineData for parameterized inputs to validate multiple scenarios in one test method.

When should I use IClassFixture vs ICollectionFixture?

Use IClassFixture for shared state scoped to a single test class. Use ICollectionFixture when multiple test classes need the same shared context, such as an integration database.

How do I test async methods reliably?

Write async test methods returning Task, use await for async calls, and implement IAsyncLifetime on fixtures for async setup and teardown to avoid race conditions.