/csharp-testing-standards
Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects.
$ npx -y skills add linuxchata/ai-playbook --skill csharp-testing-standards --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/csharp-testing-standards
Context preview
The summary Claude sees to decide when to auto-load this skill.
Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects.
SKILL.md
csharp-testing-standards.SKILL.mdname: csharp-testing-standards
description: Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects.
metadata:
version: 1.1.0
C# Testing Standards
Description
Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects.
---
1. Framework & Tooling
- **Test framework**: NUnit 3
- **Mocking**: Moq
- **Coverage**: coverlet.collector
- **Test runner**: `Microsoft.NET.Test.Sdk` + `NUnit3TestAdapter`
Core NUnit attributes in use:
| Attribute | Purpose | |---|---| | `[TestFixture]` | Marks the test class | | `[Test]` | Marks a single test method | | `[SetUp]` | Runs before each test | | `[TearDown]` | Runs after each test | | `[OneTimeSetUp]` | Runs once before all tests in the fixture | | `[TestCase]` | Parameterized test inline values |
---
2. Test Class Structure
2.1 Visibility
Test classes are `internal`. They do not need to be `public` – NUnit discovers them via the test runner regardless.
[TestFixture]
internal class OrderServiceTests { }2.2 System Under Test Field
Name the field under test `_sut` (system under test) and initialize it in `[SetUp]`:
private OrderService _sut = null!;
2.3 Mock Fields
Declare all mocks as class-level fields initialized in `[SetUp]`:
private Mock<IOrderRepository> _orderRepositoryMock = null!;
private Mock<ILogger<OrderService>> _loggerMock = null!;
2.4 SetUp Method
- Initialize all mocks and the SUT in the `[SetUp]` method.
- Configure **happy-path** default behaviors here so individual tests only override what they specifically need.
- Keep `[SetUp]` focused – it should not contain assertions or complex logic.
[SetUp]
public void Setup()
{
_orderRepositoryMock = new Mock<IOrderRepository>();
_orderRepositoryMock
.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
_sut = new OrderService(
_orderRepositoryMock.Object,
NullLogger<OrderService>.Instance);
}---
3. Test Method Naming
3.1 Pattern
MethodName_WhenCondition_ThenExpectedBehavior
- **MethodName** – the method being tested (must match the implementation exactly).
- **WhenCondition** – the specific input state or pre-condition being exercised.
- **ThenExpectedBehavior** – the exact outcome that is asserted.
// ✅ Correct
public async Task GetByIdAsync_WhenOrderDoesNotExist_ThenReturnsNull()
public async Task CreateAsync_WhenRequestIsValid_ThenPersistsAndReturnsSuccess()
public void Validate_WhenAmountIsNegative_ThenReturnsInvalidResult()
// ❌ Wrong – vague and non-descriptive
public async Task TestGetOrder()
public async Task CreateOrder_Success()
3.2 Async Test Methods
All async test methods that are `async Task` must also follow the `Async` suffix rule:
// ✅ Correct
public async Task CreateAsync_WhenRequestIsValid_ThenReturnsSuccess()
// ❌ Wrong
public async Task Create_WhenRequestIsValid_ThenReturnsSuccess()
---
4. Arrange / Act / Assert
Every test body must follow the three-section AAA structure, with explicit comments:
[Test]
public async Task GetByIdAsync_WhenOrderExists_ThenReturnsOrder()
{
// Arrange
var orderId = Guid.NewGuid();
var order = new Order { Id = orderId, CustomerName = "Alice" };
_orderRepositoryMock
.Setup(r => r.GetByIdAsync(orderId, It.IsAny<CancellationToken>()))
.ReturnsAsync(order);
// Act
var result = await _sut.GetByIdAsync(orderId, It.IsAny<CancellationToken>());
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.CustomerName, Is.EqualTo("Alice"));
}---
5. Assertions
5.1 Constraint Model
Always use `Assert.That(actual, constraint)` – the NUnit constraint model. Avoid the classic assertion API:
// ✅ Correct – constraint model
Assert.That(result, Is.Not.Null);
Assert.That(result.IsValid, Is.True);
Assert.That(result.Message, Is.Null);
Assert.That(items, Has.Length.EqualTo(2));
Assert.That(items, Is.EquivalentTo(expected));
// ❌ Avoid – classic API
Assert.IsNotNull(result);
Assert.IsTrue(result.IsValid);
Assert.AreEqual(2, items.Length);
5.2 Exception Assertions
Use `Assert.ThrowsAsync<T>` for async methods that are expected to throw:
Assert.ThrowsAsync<ArgumentNullException>(
() => _sut.CreateAsync(null!, It.IsAny<CancellationToken>()));5.3 Mock Verification
Use `.Verify()` only when asserting that a side-effecting call was (or was not) made. Do not use `.Verify()` as a substitute for return value assertions:
// ✅ Correct – asserting a save was triggered exactly once
_orderRepositoryMock.Verify(
r => r.SaveAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()),
Times.Once);
// ✅ Correct – asserting a call was never made
_orderRepositoryMock.Verify(
r => r.DeleteAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()),
Times.Never);---
6. Parameterized Tests
Use `[TestCase]` for boundary values (null, empty, whitespace, zero, negative) rather than duplicating test logic:
[TestCase(null!)]
[TestCase("")]
[TestCase(" ")]
public async Task CreateAsync_WhenCustomerNameIsEmpty_ThenReturnsFailure(string customerName)
{
// Arrange
var request = new CreateOrderRequest { CustomerName = customerName };
// Act
var result = await _sut.CreateAsync(request, It.IsAny<CancellationToken>());
// Assert
Assert.That(result.IsValid, Is.False);
}For multiple varying inputs, prefer `[TestCaseSource]` over stacking many `[Tes
Read more
name: csharp-testing-standards description: Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects. metadata: version: 1.1.0
C# Testing Standards
Description
Defines the testing standards, patterns, and conventions for all C# unit and integration test projects. Rules cover test framework usage, naming, structure, mocking, assertions, and parameterization. Apply these rules uniformly across all test projects.
---
1. Framework & Tooling
- **Test framework**: NUnit 3
- **Mocking**: Moq
- **Coverage**: coverlet.collector
- **Test runner**: `Microsoft.NET.Test.Sdk` + `NUnit3TestAdapter`
Core NUnit attributes in use:
| Attribute | Purpose | |---|---| | `[TestFixture]` | Marks the test class | | `[Test]` | Marks a single test method | | `[SetUp]` | Runs before each test | | `[TearDown]` | Runs after each test | | `[OneTimeSetUp]` | Runs once before all tests in the fixture | | `[TestCase]` | Parameterized test inline values |
---
2. Test Class Structure
2.1 Visibility
Test classes are `internal`. They do not need to be `public` – NUnit discovers them via the test runner regardless.
[TestFixture]
internal class OrderServiceTests { }2.2 System Under Test Field
Name the field under test `_sut` (system under test) and initialize it in `[SetUp]`:
private OrderService _sut = null!;
2.3 Mock Fields
Declare all mocks as class-level fields initialized in `[SetUp]`:
private Mock<IOrderRepository> _orderRepositoryMock = null!; private Mock<ILogger<OrderService>> _loggerMock = null!;
2.4 SetUp Method
- Initialize all mocks and the SUT in the `[SetUp]` method.
- Configure **happy-path** default behaviors here so individual tests only override what they specifically need.
- Keep `[SetUp]` focused – it should not contain assertions or complex logic.
[SetUp]
public void Setup()
{
_orderRepositoryMock = new Mock<IOrderRepository>();
_orderRepositoryMock
.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
_sut = new OrderService(
_orderRepositoryMock.Object,
NullLogger<OrderService>.Instance);
}---
3. Test Method Naming
3.1 Pattern
MethodName_WhenCondition_ThenExpectedBehavior
- **MethodName** – the method being tested (must match the implementation exactly).
- **WhenCondition** – the specific input state or pre-condition being exercised.
- **ThenExpectedBehavior** – the exact outcome that is asserted.
// ✅ Correct public async Task GetByIdAsync_WhenOrderDoesNotExist_ThenReturnsNull() public async Task CreateAsync_WhenRequestIsValid_ThenPersistsAndReturnsSuccess() public void Validate_WhenAmountIsNegative_ThenReturnsInvalidResult() // ❌ Wrong – vague and non-descriptive public async Task TestGetOrder() public async Task CreateOrder_Success()
3.2 Async Test Methods
All async test methods that are `async Task` must also follow the `Async` suffix rule:
// ✅ Correct public async Task CreateAsync_WhenRequestIsValid_ThenReturnsSuccess() // ❌ Wrong public async Task Create_WhenRequestIsValid_ThenReturnsSuccess()
---
4. Arrange / Act / Assert
Every test body must follow the three-section AAA structure, with explicit comments:
[Test]
public async Task GetByIdAsync_WhenOrderExists_ThenReturnsOrder()
{
// Arrange
var orderId = Guid.NewGuid();
var order = new Order { Id = orderId, CustomerName = "Alice" };
_orderRepositoryMock
.Setup(r => r.GetByIdAsync(orderId, It.IsAny<CancellationToken>()))
.ReturnsAsync(order);
// Act
var result = await _sut.GetByIdAsync(orderId, It.IsAny<CancellationToken>());
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result!.CustomerName, Is.EqualTo("Alice"));
}---
5. Assertions
5.1 Constraint Model
Always use `Assert.That(actual, constraint)` – the NUnit constraint model. Avoid the classic assertion API:
// ✅ Correct – constraint model Assert.That(result, Is.Not.Null); Assert.That(result.IsValid, Is.True); Assert.That(result.Message, Is.Null); Assert.That(items, Has.Length.EqualTo(2)); Assert.That(items, Is.EquivalentTo(expected)); // ❌ Avoid – classic API Assert.IsNotNull(result); Assert.IsTrue(result.IsValid); Assert.AreEqual(2, items.Length);
5.2 Exception Assertions
Use `Assert.ThrowsAsync<T>` for async methods that are expected to throw:
Assert.ThrowsAsync<ArgumentNullException>(
() => _sut.CreateAsync(null!, It.IsAny<CancellationToken>()));5.3 Mock Verification
Use `.Verify()` only when asserting that a side-effecting call was (or was not) made. Do not use `.Verify()` as a substitute for return value assertions:
// ✅ Correct – asserting a save was triggered exactly once
_orderRepositoryMock.Verify(
r => r.SaveAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()),
Times.Once);
// ✅ Correct – asserting a call was never made
_orderRepositoryMock.Verify(
r => r.DeleteAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()),
Times.Never);---
6. Parameterized Tests
Use `[TestCase]` for boundary values (null, empty, whitespace, zero, negative) rather than duplicating test logic:
[TestCase(null!)]
[TestCase("")]
[TestCase(" ")]
public async Task CreateAsync_WhenCustomerNameIsEmpty_ThenReturnsFailure(string customerName)
{
// Arrange
var request = new CreateOrderRequest { CustomerName = customerName };
// Act
var result = await _sut.CreateAsync(request, It.IsAny<CancellationToken>());
// Assert
Assert.That(result.IsValid, Is.False);
}For multiple varying inputs, prefer `[TestCaseSource]` over stacking many `[Tes
Showing the first part of this file.
Rules, skills, and guidelines for AI coding assistants – Claude, Cursor, and beyond.
Repo: linuxchata/ai-playbook
Other skills on ai-playbook.
- /csharp-api-controller-standards
Defines the coding standards, patterns, and conventions for ASP.NET Core REST API controllers. Rules cover routing, HTTP verbs, response types, XML documentation, dependency injection, and asynchronous execution. Apply these rules uniformly to ensure a consistent, predictable,
Open skill - /csharp-coding-standards
Defines the C# coding standards, patterns, and conventions to be applied consistently across all C# projects. Rules cover naming, structure, async patterns, null handling, dependency injection, logging, result patterns, and formatting. Apply these rules uniformly in all
Open skill

