csharp-testing-standards-25983b — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited csharp-testing-standards-25983b (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
Microsoft.NET.Test.Sdk + NUnit3TestAdapterCore 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] method.[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);
}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 [TestCase] attributes:
private static IEnumerable<TestCaseData> InvalidAmounts()
{
yield return new TestCaseData(0m).SetName("Zero");
yield return new TestCaseData(-1m).SetName("Negative");
yield return new TestCaseData(decimal.MinValue).SetName("MinValue");
}
[TestCaseSource(nameof(InvalidAmounts))]
public void Validate_WhenAmountIsInvalid_ThenReturnsInvalidResult(decimal amount) { ... }Only mock types that your code directly depends on (interfaces, abstract classes in your own codebase). Never mock types owned by third-party libraries or the .NET runtime.
Use the default loose mock behavior (MockBehavior.Default). Use MockBehavior.Strict only when you need to assert that no unexpected calls are made.
It.IsAny<T>() in SetUpConfigure broad default behaviours in [SetUp] using It.IsAny<T>(). Narrow down to exact arguments only in tests that specifically need it:
// SetUp – broad default
_repositoryMock
.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
// Individual test – specific input
_repositoryMock
.Setup(r => r.GetByIdAsync(specificId, It.IsAny<CancellationToken>()))
.ReturnsAsync(specificOrder);NullLogger<T> in TestsNever mock ILogger<T>. Use NullLogger<T>.Instance from Microsoft.Extensions.Logging.Abstractions:
_sut = new OrderService(_repositoryMock.Object, NullLogger<OrderService>.Instance);Each production project has a corresponding test project with .Tests appended to the name:
MyApp.Core → MyApp.Core.Tests
MyApp.DataAccess → MyApp.DataAccess.TestsMirror the folder structure of the production project inside the test project. Each production class has a corresponding <ClassName>Tests.cs file in the same relative folder.
Extract repeated object construction into dedicated builder classes or static factory methods to keep tests readable:
internal static class OrderBuilder
{
public static Order Build(Guid? id = null, string customerName = "Default Customer")
{
return new Order
{
Id = id ?? Guid.NewGuid(),
CustomerName = customerName,
TotalAmount = 100m,
};
}
}Before submitting any test file, verify:
internal with [TestFixture]_sut and initialized in [SetUp][SetUp]MethodName_WhenCondition_ThenExpectedBehavior// Arrange, // Act, // Assert sectionsAssert.That(...) constraint modelILogger<T> is not mocked – NullLogger<T>.Instance used instead[TestCase] used for boundary values, not duplicate test methods.Verify() used only for side-effect assertions, not return value checks~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.