Claude Agent Skill · by Affaan M

Csharp Testing

Install Csharp Testing skill for Claude Code from affaan-m/everything-claude-code.

Install
Terminal · npx
$npx skills add https://github.com/obra/superpowers --skill test-driven-development
Works with Paperclip

How Csharp Testing fits into a Paperclip company.

Csharp Testing drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md321 lines
Expand
---name: csharp-testingdescription: C# and .NET testing patterns with xUnit, FluentAssertions, mocking, integration tests, and test organization best practices.origin: ECC--- # 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 ```csharppublic 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 ```csharp[Theory][InlineData("", false)][InlineData("a", false)][InlineData("ab@c.d", false)][InlineData("user@example.com", true)][InlineData("user+tag@example.co.uk", 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 ```csharp[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 ```csharppublic 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 ```csharppublic 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 ```csharppublic 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 testsvar 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 ```bash# Run all testsdotnet test # Run with coveragedotnet test --collect:"XPlat Code Coverage" # Run specific projectdotnet test tests/MyApp.UnitTests/ # Filter by test namedotnet test --filter "FullyQualifiedName~OrderService" # Watch mode during developmentdotnet watch test --project tests/MyApp.UnitTests/```