
testing
Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".
- Installs
- 2.9K
- Last 24h
- +55
- Trending rank
- #3149
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill testing- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads testing automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Testing (.NET 10)
Core Principles
- Integration tests are the highest-value tests — A single
WebApplicationFactorytest covers routing, binding, validation, business logic, and persistence in one shot. Start here before writing unit tests. - Real databases in tests — Use Testcontainers to spin up real PostgreSQL/SQL Server instances. In-memory providers hide real bugs (transactions, constraints, SQL generation).
- AAA pattern is mandatory — Every test has three clearly separated sections: Arrange, Act, Assert. No mixing.
- Test behavior, not implementation — Tests should survive refactoring. Test what the system does, not how it does it.
Patterns
xUnit v3 Basics
public class OrderServiceTests
{
[Fact]
public async Task CreateOrder_WithValidItems_ReturnsSuccessResult()
{
// Arrange
var db = CreateInMemoryDb();
var clock = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 0, 0, 0, TimeSpan.Zero));
var service = new OrderService(db, clock);
var request = new CreateOrderRequest("customer-1", [new("product-1", 2)]);
// Act
var result = await service.CreateAsync(request);
// Assert
Assert.True(result.IsSuccess);
Assert.NotEqual(Guid.Empty, result.Value.Id);
Assert.Equal(clock.GetUtcNow(), result.Value.CreatedAt);
}
[Theory]
[InlineData("")]
[InlineData(null)]
public async Task CreateOrder_WithInvalidCustomerId_ReturnsFailure(string? customerId)
{
// Arrange
var service = CreateService();
// Act
var result = await service.CreateAsync(new CreateOrderRequest(customerId!, []));
// Assert
Assert.False(result.IsSuccess);
}
}
Integration Tests with WebApplicationFactory
The highest-value test pattern. Tests the full HTTP pipeline.
// Fixtures/ApiFixture.cs
public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:18")
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Replace the real DB with Testcontainers
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(_postgres.GetConnectionString()));
});
}
// xUnit v3: IAsyncLifetime.InitializeAsync returns ValueTask (v2 used Task)
public async ValueTask InitializeAsync()
{
await _postgres.StartAsync();
// Apply migrations
using var scope = Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}
// xUnit v3: IAsyncLifetime inherits IAsyncDisposable — override the
// ValueTask DisposeAsync that WebApplicationFactory already provides
public override async ValueTask DisposeAsync()
{
await _postgres.DisposeAsync();
await base.DisposeAsync();
}
}
// Tests/Orders/CreateOrderTests.cs
public class CreateOrderTests(ApiFixture fixture) : IClassFixture<ApiFixture>
{
private readonly HttpClient _client = fixture.CreateClient();
[Fact]
public async Task CreateOrder_ReturnsCreated_WithValidRequest()
{
// Arrange
var request = new CreateOrderRequest("customer-1", [new("product-1", 2)]);
// Act
var response = await _client.PostAsJsonAsync("/api/orders", request);
// Assert
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var order = await response.Content.ReadFromJsonAsync<OrderResponse>();
Assert.NotNull(order);
Assert.NotEqual(Guid.Empty, order.Id);
Assert.Contains("/api/orders/", response.Headers.Location?.ToString());
}
[Fact]
public async Task CreateOrder_ReturnsValidationProblem_WithEmptyItems()
{
// Arrange
var request = new CreateOrderRequest("customer-1", []);
// Act
var response = await _client.PostAsJsonAsync("/api/orders", request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
}
Testcontainers for Real Database Testing
// For SQL Server
private readonly MsSqlContainer _mssql = new MsSqlBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.Build();
// For PostgreSQL
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
.WithImage("postgres:18")
.Build();
// For Redis
private readonly RedisContainer _redis = new RedisBuilder()
.WithImage("redis:7")
.Build();
Verify Snapshot Testing
Use Verify for complex response objects where manual assertions would be fragile.
[Fact]
public async Task GetOrder_MatchesSnapshot()
{
// Arrange
await SeedOrder(fixture);
// Act
var response = await _client.GetAsync("/api/orders/known-id");
var content = await response.Content.ReadAsStringAsync();
// Assert — compares against a stored .verified.txt file
await Verify(content);
}
On first run, Verify creates a .verified.txt file. On subsequent runs, it compares output. If the output changes, the test fails and shows a diff.
Test Data Builders
public class OrderBuilder
{
private string _customerId = "default-customer";
private List<OrderItem> _items = [new("product-1", 1, 9.99m)];
private OrderStatus _status = OrderStatus.Pending;
public OrderBuilder WithCustomer(string customerId)
{
_customerId = customerId;
return this;
}
public OrderBuilder WithItems(params OrderItem[] items)
{
_items = [..items];
return this;
}
public OrderBuilder WithStatus(OrderStatus status)
{
_status = status;
return this;
}
public Order Build() => Order.Create(_customerId, _items, _status);
}
// Usage in tests
var order = new OrderBuilder()
.WithCustomer("vip-customer")
.WithStatus(OrderStatus.Confirmed)
.Build();
Testing Time-Dependent Code
Use TimeProvider (built into .NET 8+) and FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing.
[Fact]
public async Task ExpireOrders_MarksOldPendingOrdersAsExpired()
{
// Arrange
var clock = new FakeTimeProvider(new DateTimeOffset(2025, 6, 1, 0, 0, 0, TimeSpan.Zero));
var db = CreateDb();
var order = Order.Create("customer-1", items, clock.GetUtcNow());
db.Orders.Add(order);
await db.SaveChangesAsync();
// Advance time past expiry threshold
clock.Advance(TimeSpan.FromDays(31));
var handler = new ExpireOrders.Handler(db, clock);
// Act
await handler.Handle(new ExpireOrders.Command(), CancellationToken.None);
// Assert
var updated = await db.Orders.FindAsync(order.Id);
Assert.Equal(OrderStatus.Expired, updated!.Status);
}
Test Naming Convention
Use the pattern: MethodName_StateUnderTest_ExpectedBehavior
[Fact] public async Task CreateOrder_WithValidItems_ReturnsSuccessResult() { }
[Fact] public async Task CreateOrder_WithEmptyItems_ReturnsValidationError() { }
[Fact] public async Task GetOrder_WithNonExistentId_ReturnsNotFound() { }
[Fact] public async Task CancelOrder_WhenAlreadyShipped_ReturnsConflict() { }
Anti-patterns
Don't Use In-Memory Database for Integration Tests
// BAD — hides real SQL behavior, transactions, constraints
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// GOOD — Testcontainers with real database
services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(testContainer.GetConnectionString()));
Don't Test Implementation Details
// BAD — testing that a specific repository method was called
mock.Verify(x => x.AddAsync(It.IsAny<Order>()), Times.Once);
mock.Verify(x => x.SaveChangesAsync(), Times.Once);
// GOOD — test the observable outcome
var order = await db.Orders.FindAsync(orderId);
Assert.NotNull(order);
Assert.Equal(OrderStatus.Created, order.Status);
Don't Share Mutable State Between Tests
// BAD — static shared state
private static readonly AppDbContext SharedDb = CreateDb();
// GOOD — fresh state per test (or use IAsyncLifetime for shared fixtures)
private AppDbContext CreateDb() => new(new DbContextOptionsBuilder<AppDbContext>()...);
Don't Write Assertion-Free Tests
// BAD — no assertion, only checks it doesn't throw
[Fact]
public async Task CreateOrder_Works()
{
await service.CreateAsync(request);
// "it didn't throw, so it works!" — NO
}
// GOOD — assert the expected outcome
[Fact]
public async Task CreateOrder_PersistsOrderToDatabase()
{
var result = await service.CreateAsync(request);
var persisted = await db.Orders.FindAsync(result.Value.Id);
Assert.NotNull(persisted);
Assert.Equal(request.CustomerId, persisted.CustomerId);
}
Decision Guide
| Scenario | Recommendation |
|---|---|
| Testing an API endpoint | WebApplicationFactory integration test |
| Testing business logic in isolation | Unit test with fakes/stubs |
| Database-dependent tests | Testcontainers (real DB) |
| Complex response validation | Verify snapshot testing |
| Time-dependent logic | FakeTimeProvider |
| External API dependency | WireMock.Net or HttpMessageHandler stub |
| Parameterized test cases | [Theory] with [InlineData] or [MemberData] |
| Test data setup | Builder pattern |
| Shared expensive fixture | IClassFixture<T> with IAsyncLifetime |
FAQ
What does the testing skill do?
Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".
How do I install testing?
Run npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill testing in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/testing/ (personal) or .claude/skills/testing/ (project).
Which AI agents can use testing?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is testing safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains testing?
It is published by codewithmukesh in codewithmukesh/dotnet-claude-kit. It has 2,891 recorded installs.
More from codewithmukesh/dotnet-claude-kit
clean-architecture
codewithmukesh/dotnet-claude-kit
Clean Architecture for .NET applications. Covers the 4-project layout (Domain, Application, Infrastructure, Api), dependency inversion, use case handlers, domain entities with behavior, and infrastructure as a plugin. Load this skill when building a project with Clean Architecture, discussing layered architecture, dependency inversion, use cases, or when the architecture-advisor recommends Clean Architecture.
ddd
codewithmukesh/dotnet-claude-kit
Domain-Driven Design tactical patterns for .NET applications. Covers aggregates, aggregate roots, value objects, domain events, domain services, strongly-typed IDs, and repository patterns for aggregate persistence. Load this skill when implementing DDD, working with aggregates, value objects, domain events, bounded contexts, or when the architecture-advisor recommends DDD + Clean Architecture. Pair with the clean-architecture skill.
modern-csharp
codewithmukesh/dotnet-claude-kit
Modern C language features for .NET 10 and C 14. Covers primary constructors, collection expressions, the field keyword, extension members, records, pattern matching, spans, and raw string literals. Load this skill when writing any new C code, reviewing existing code for modernization, using "modern C", "C 14", "primary constructor", "collection expression", "records", "pattern matching", "span", "field keyword", or "extension members". Always loaded as the baseline for all agents.
vertical-slice
codewithmukesh/dotnet-claude-kit
Vertical Slice Architecture (VSA) for .NET applications — one of several supported architectures in dotnet-claude-kit. Covers feature folders, endpoint grouping, and handler patterns for Mediator, Wolverine, and raw handler classes. Load this skill when the architecture-advisor recommends VSA, when working in an existing VSA codebase, when adding features to a feature-folder project, or when discussing vertical slice patterns, feature folders, or handler patterns.
security-scan
codewithmukesh/dotnet-claude-kit
Deep security scanning for .NET applications across 6 layers: vulnerable packages, secrets detection, OWASP code patterns, auth configuration, CORS policy, and data protection. Produces severity-rated findings with specific remediation steps. Load this skill when: "security scan", "security audit", "check for vulnerabilities", "find secrets", "OWASP", "auth review", "CORS check", "security review", "penetration test prep", "CVE check", "vulnerability scan", "hardcoded password", "data protection", "security posture".
serilog
codewithmukesh/dotnet-claude-kit
Structured logging with Serilog for .NET 10 applications. Covers two-stage bootstrap, appsettings configuration, enrichers, sinks, request logging, destructuring, and Serilog.Expressions. Load this skill when setting up Serilog, configuring log sinks, enrichers, or structured logging, or when the user mentions "Serilog", "structured logging", "log enrichment", "Seq", "LogContext", "UseSerilog", "WriteTo", "message template", "Serilog.Expressions", "request logging", "log sink", "rolling file", or "audit log".
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work