csharp-testing
Provides patterns and tool recommendations for testing .NET applications, including mocking, integration, and data generation.
Install
mkdir -p .claude/skills/csharp-testing && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/17211" && unzip -o skill.zip -d .claude/skills/csharp-testing && rm skill.zipInstalls to .claude/skills/csharp-testing
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
C# and .NET testing patterns with xUnit, FluentAssertions, mocking, integration tests, and test organization best practices.Key capabilities
- →Write unit tests using xUnit and FluentAssertions
- →Implement parameterized tests with Theory and InlineData/MemberData
- →Mock dependencies using NSubstitute
- →Set up ASP.NET Core integration tests with WebApplicationFactory
- →Conduct integration tests with Testcontainers for real infrastructure
- →Organize test files and use test data builders
How it works
The skill provides patterns and examples for writing C# tests using xUnit, FluentAssertions, NSubstitute, WebApplicationFactory, and Testcontainers, covering unit and integration testing.
Inputs & outputs
When to use csharp-testing
- →Writing C# unit tests
- →Configuring test infrastructure
- →Integration testing with databases
- →Generating realistic test data
About this skill
C# Testing Patterns
Comprehensive testing patterns for .NET applications using xUnit, FluentAssertions, and modern testing practices.
When to Activate
- Writing new tests for C# code
- Reviewing test quality and coverage
- Setting up test infrastructure for .NET projects
- Debugging flaky or slow tests
Test Framework Stack
| Tool | Purpose |
|---|---|
| xUnit | Test framework (preferred for .NET) |
| FluentAssertions | Readable assertion syntax |
| NSubstitute or Moq | Mocking dependencies |
| Testcontainers | Real infrastructure in integration tests |
| WebApplicationFactory | ASP.NET Core integration tests |
| Bogus | Realistic test data generation |
Unit Test Structure
Arrange-Act-Assert
public sealed class OrderServiceTests
{
private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
private readonly ILogger<OrderService> _logger = Substitute.For<ILogger<OrderService>>();
private readonly OrderService _sut;
public OrderServiceTests()
{
_sut = new OrderService(_repository, _logger);
}
[Fact]
public async Task PlaceOrderAsync_ReturnsSuccess_WhenRequestIsValid()
{
// Arrange
var request = new CreateOrderRequest
{
CustomerId = "cust-123",
Items = [new OrderItem("SKU-001", 2, 29.99m)]
};
// Act
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
result.Value!.CustomerId.Should().Be("cust-123");
}
[Fact]
public async Task PlaceOrderAsync_ReturnsFailure_WhenNoItems()
{
// Arrange
var request = new CreateOrderRequest
{
CustomerId = "cust-123",
Items = []
};
// Act
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("at least one item");
}
}
Parameterized Tests with Theory
[Theory]
[InlineData("", false)]
[InlineData("a", false)]
[InlineData("[email protected]", false)]
[InlineData("[email protected]", true)]
[InlineData("[email protected]", true)]
public void IsValidEmail_ReturnsExpected(string email, bool expected)
{
EmailValidator.IsValid(email).Should().Be(expected);
}
[Theory]
[MemberData(nameof(InvalidOrderCases))]
public async Task PlaceOrderAsync_RejectsInvalidOrders(CreateOrderRequest request, string expectedError)
{
var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain(expectedError);
}
public static TheoryData<CreateOrderRequest, string> InvalidOrderCases => new()
{
{ new() { CustomerId = "", Items = [ValidItem()] }, "CustomerId" },
{ new() { CustomerId = "c1", Items = [] }, "at least one item" },
{ new() { CustomerId = "c1", Items = [new("", 1, 10m)] }, "SKU" },
};
Mocking with NSubstitute
[Fact]
public async Task GetOrderAsync_ReturnsNull_WhenNotFound()
{
// Arrange
var orderId = Guid.NewGuid();
_repository.FindByIdAsync(orderId, Arg.Any<CancellationToken>())
.Returns((Order?)null);
// Act
var result = await _sut.GetOrderAsync(orderId, CancellationToken.None);
// Assert
result.Should().BeNull();
}
[Fact]
public async Task PlaceOrderAsync_PersistsOrder()
{
// Arrange
var request = ValidOrderRequest();
// Act
await _sut.PlaceOrderAsync(request, CancellationToken.None);
// Assert — verify the repository was called
await _repository.Received(1).AddAsync(
Arg.Is<Order>(o => o.CustomerId == request.CustomerId),
Arg.Any<CancellationToken>());
}
ASP.NET Core Integration Tests
WebApplicationFactory Setup
public sealed class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrderApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace real DB with in-memory for tests
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
});
}).CreateClient();
}
[Fact]
public async Task GetOrder_Returns404_WhenNotFound()
{
var response = await _client.GetAsync($"/api/orders/{Guid.NewGuid()}");
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task CreateOrder_Returns201_WithValidRequest()
{
var request = new CreateOrderRequest
{
CustomerId = "cust-1",
Items = [new("SKU-001", 1, 19.99m)]
};
var response = await _client.PostAsJsonAsync("/api/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.Created);
response.Headers.Location.Should().NotBeNull();
}
}
Testing with Testcontainers
public sealed class PostgresOrderRepositoryTests : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:16-alpine")
.Build();
private AppDbContext _db = null!;
public async Task InitializeAsync()
{
await _postgres.StartAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(_postgres.GetConnectionString())
.Options;
_db = new AppDbContext(options);
await _db.Database.MigrateAsync();
}
public async Task DisposeAsync()
{
await _db.DisposeAsync();
await _postgres.DisposeAsync();
}
[Fact]
public async Task AddAsync_PersistsOrder()
{
var repo = new SqlOrderRepository(_db);
var order = Order.Create("cust-1", [new OrderItem("SKU-001", 2, 10m)]);
await repo.AddAsync(order, CancellationToken.None);
var found = await repo.FindByIdAsync(order.Id, CancellationToken.None);
found.Should().NotBeNull();
found!.Items.Should().HaveCount(1);
}
}
Test Organization
tests/
MyApp.UnitTests/
Services/
OrderServiceTests.cs
PaymentServiceTests.cs
Validators/
EmailValidatorTests.cs
MyApp.IntegrationTests/
Api/
OrderApiTests.cs
Repositories/
OrderRepositoryTests.cs
MyApp.TestHelpers/
Builders/
OrderBuilder.cs
Fixtures/
DatabaseFixture.cs
Test Data Builders
public sealed class OrderBuilder
{
private string _customerId = "cust-default";
private readonly List<OrderItem> _items = [new("SKU-001", 1, 10m)];
public OrderBuilder WithCustomer(string customerId)
{
_customerId = customerId;
return this;
}
public OrderBuilder WithItem(string sku, int quantity, decimal price)
{
_items.Add(new OrderItem(sku, quantity, price));
return this;
}
public Order Build() => Order.Create(_customerId, _items);
}
// Usage in tests
var order = new OrderBuilder()
.WithCustomer("cust-vip")
.WithItem("SKU-PREMIUM", 3, 99.99m)
.Build();
Common Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| Testing implementation details | Test behavior and outcomes |
| Shared mutable test state | Fresh instance per test (xUnit does this via constructors) |
Thread.Sleep in async tests | Use Task.Delay with timeout, or polling helpers |
Asserting on ToString() output | Assert on typed properties |
| One giant assertion per test | One logical assertion per test |
| Test names describing implementation | Name by behavior: Method_ExpectedResult_WhenCondition |
Ignoring CancellationToken | Always pass and verify cancellation |
Running Tests
# Run all tests
dotnet test
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Run specific project
dotnet test tests/MyApp.UnitTests/
# Filter by test name
dotnet test --filter "FullyQualifiedName~OrderService"
# Watch mode during development
dotnet watch test --project tests/MyApp.UnitTests/
When not to use it
- →When testing non-C# or non-.NET applications
- →When a different test framework is mandated
Limitations
- →The patterns are specific to C# and .NET
- →Relies on specific testing frameworks and libraries (xUnit, FluentAssertions, NSubstitute/Moq, Testcontainers)
- →Focuses on the Arrange-Act-Assert pattern
How it compares
This workflow offers a complete, opinionated guide to modern C# testing practices, standardizing test structure and tool usage, unlike ad-hoc testing approaches.
Compared to similar skills
csharp-testing side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| csharp-testing (this skill) | 0 | 3mo | Review | Intermediate |
| csharp-pro | 9 | 4mo | No flags | Intermediate |
| performance-benchmark | 3 | 5mo | No flags | Intermediate |
| backend-testing | 3 | 1mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
csharp-pro
sickn33
Write modern C# code with advanced features like records, pattern matching, and async/await. Optimizes .NET applications, implements enterprise patterns, and ensures comprehensive testing. Use PROACTIVELY for C# refactoring, performance optimization, or complex .NET solutions.
performance-benchmark
dotnet
Generate and run ad hoc performance benchmarks to validate code changes. Use this when asked to benchmark, profile, or validate the performance impact of a code change in dotnet/runtime.
backend-testing
exceptionless
Backend testing with xUnit, Foundatio.Xunit, integration tests with AppWebHostFactory, FluentClient, ProxyTimeProvider for time manipulation, and test data builders. Keywords: xUnit, Fact, Theory, integration tests, AppWebHostFactory, FluentClient, ProxyTimeProvider, TimeProvider, Foundatio.Xunit, TestWithLoggingBase, test data builders
dotnet-dev
GitTools
Expert guidance for .NET development in this repository. Use this skill for building, testing, debugging, and understanding project structure, coding conventions, dependency injection patterns, and testing practices.
mutation-testing
SebastienDegodez
Use when running mutation testing, killing mutants, verifying test quality, checking mutation score, or analyzing survivors after the test baseline is green
quality-ci
managedcode
Set up or refine open-source .NET code-quality gates for CI: formatting, `.editorconfig`, SDK analyzers, third-party analyzers, coverage, mutation testing, architecture tests, and security scanning. USE FOR: .NET quality gates in CI; analyzer, coverage, mutation, and architecture-test choices; stand