From backend-csharp
Use when writing tests for C# code — xUnit or NUnit with FluentAssertions, Moq, AutoFixture, and Testcontainers patterns including test naming, arrangement, and coverage conventions
How this skill is triggered — by the user, by Claude, or both
Slash command
/backend-csharp:csharp-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
| Purpose | Primary | Alternative |
| Purpose | Primary | Alternative |
|---|---|---|
| Test runner | xUnit | NUnit |
| Assertions | FluentAssertions | xUnit built-in |
| Mocking | Moq | NSubstitute |
| Fixtures | AutoFixture | manual builders |
| Integration | Testcontainers | WebApplicationFactory |
// Pattern: MethodName_Condition_ExpectedResult
[Fact]
public async Task GetOrderAsync_WhenOrderExists_ReturnsOrder()
[Fact]
public async Task GetOrderAsync_WhenOrderNotFound_ReturnsNull()
[Fact]
public async Task CreateOrder_WithInvalidItems_ReturnsValidationError()
[Fact]
public async Task CreateOrder_WithValidCommand_ReturnsOrderId()
{
// Arrange
var command = new CreateOrderCommand("customer-1", new List<OrderItem>
{
new("product-1", Quantity: 2, Price: 29.99m)
});
_repositoryMock.Setup(r => r.AddAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OrderId(42));
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be(new OrderId(42));
_repositoryMock.Verify(r => r.AddAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Once);
}
It.IsAny<T>() for flexible matching, It.Is<T>(predicate) for specificVerify critical interactionspublic class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace real DB with test container or in-memory
});
}).CreateClient();
}
[Fact]
public async Task CreateOrder_ReturnsCreated()
{
var response = await _client.PostAsJsonAsync("/api/v1/orders", new { /* ... */ });
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
}
Structure each test class to cover positive, negative, edge, and security scenarios.
[Fact]
public async Task CreateOrder_WithValidCommand_ReturnsOrderId()
{
// Arrange
var command = new CreateOrderCommand("customer-1",
new List<OrderItem> { new("product-1", Quantity: 2, Price: 29.99m) });
_repositoryMock.Setup(r => r.AddAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OrderId(42));
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be(new OrderId(42));
}
[Fact]
public async Task CreateOrder_WithEmptyItems_ReturnsValidationError()
{
var command = new CreateOrderCommand("customer-1", new List<OrderItem>());
var result = await _handler.Handle(command, CancellationToken.None);
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("Items cannot be empty");
}
[Fact]
public async Task GetOrder_WhenNotFound_ReturnsNull()
{
_repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<OrderId>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Order?)null);
var result = await _handler.Handle(new GetOrderQuery(new OrderId(999)), CancellationToken.None);
result.Should().BeNull();
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(int.MaxValue)]
public async Task CreateOrder_WithBoundaryQuantity_HandlesCorrectly(int quantity)
{
var command = new CreateOrderCommand("customer-1",
new List<OrderItem> { new("product-1", Quantity: quantity, Price: 10m) });
var result = await _handler.Handle(command, CancellationToken.None);
if (quantity <= 0)
result.IsSuccess.Should().BeFalse();
else
result.IsSuccess.Should().BeTrue();
}
[Fact]
public async Task CreateOrder_WithMaxLengthCustomerId_Succeeds()
{
var longId = new string('c', 255);
var command = new CreateOrderCommand(longId,
new List<OrderItem> { new("p1", 1, 10m) });
var result = await _handler.Handle(command, CancellationToken.None);
result.IsSuccess.Should().BeTrue();
}
[Theory]
[InlineData("'; DROP TABLE Orders; --")]
[InlineData("<script>alert('xss')</script>")]
[InlineData("../../../etc/passwd")]
public async Task CreateOrder_WithMaliciousCustomerId_RejectsOrSanitizes(string maliciousInput)
{
var command = new CreateOrderCommand(maliciousInput,
new List<OrderItem> { new("p1", 1, 10m) });
var result = await _handler.Handle(command, CancellationToken.None);
result.IsSuccess.Should().BeFalse();
result.Error.Should().NotContain("SQL");
result.Error.Should().NotContain("Exception");
}
[Fact]
public async Task GetOrder_WithoutAuthorization_ReturnsUnauthorized()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v1/orders/1");
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
var body = await response.Content.ReadAsStringAsync();
body.Should().NotContain("stack trace");
body.Should().NotContain("connection string");
}
[Fact]
public async Task CreateOrder_WithExtraFields_IgnoresMassAssignment()
{
var json = """{"customerId":"c1","items":[{"productId":"p1","quantity":1,"price":10}],"isAdmin":true,"discount":100}""";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/api/v1/orders", content);
response.StatusCode.Should().Be(HttpStatusCode.Created);
var order = await response.Content.ReadFromJsonAsync<OrderResponse>();
order!.Discount.Should().Be(0m);
}
npx claudepluginhub gagandeepp/software-agent-teams --plugin backend-csharpGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.