csharp-api-controller-…
Defines the coding standards, patterns, and conventions for ASP.NET Core REST API controllers. Rules cover routing, HTTP verbs, response types, XML…
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.
/csharp-testing-standardsContext 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.
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
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.
---
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 |
---
Test classes are `internal`. They do not need to be `public` – NUnit discovers them via the test runner regardless.
[TestFixture]
internal class OrderServiceTests { }Name the field under test `_sut` (system under test) and initialize it in `[SetUp]`:
private OrderService _sut = null!;
Declare all mocks as class-level fields initialized in `[SetUp]`:
private Mock<IOrderRepository> _orderRepositoryMock = null!; private Mock<ILogger<OrderService>> _loggerMock = null!;
[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);
}---
MethodName_WhenCondition_ThenExpectedBehavior
// ✅ 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()
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()
---
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"));
}---
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);
Use `Assert.ThrowsAsync<T>` for async methods that are expected to throw:
Assert.ThrowsAsync<ArgumentNullException>(
() => _sut.CreateAsync(null!, It.IsAny<CancellationToken>()));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);---
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
Rules, skills, and guidelines for AI coding assistants – Claude, Cursor, and beyond.
Repo: linuxchata/ai-playbook
Defines the coding standards, patterns, and conventions for ASP.NET Core REST API controllers. Rules cover routing, HTTP verbs, response types, XML…
Defines the C# coding standards, patterns, and conventions to be applied consistently across all C# projects. Rules cover naming, structure, async patterns,…