Install
$ agentstack add skill-iambrzdev-enterprise-agent-skills-dotnet-testing ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
When to activate
- Writing a unit test or integration test in .NET / C#
- "What should I mock?" in a .NET testing context
- Setting up a test project for the first time in a solution
- Configuring WebApplicationFactory for ASP.NET Core API tests
- Implementing database isolation for integration tests
- Testing a MediatR Command, Query, or Handler
- Testing FluentValidation validators
- Setting up shared test infrastructure (Docker containers, DB migrations)
- Any question about xUnit fixtures, test collections, or parallel execution
- "How do I test this without hitting the real database?"
Rules — Non-negotiable
- One Act per test. Strict AAA pattern: one action triggers one outcome.
A test that does two things hides which one failed.
- Mock only external/unmanaged dependencies. Moq is for third-party APIs,
email services, payment gateways — not for repositories or domain services. Test those with real implementations.
- Integration tests use real databases. In-memory providers (UseInMemoryDatabase)
do not enforce foreign keys, SQL constraints, or stored procedures. Use a real SQL Server instance (Docker) for integration tests.
- Every test leaves zero side effects. Use transaction rollback or database
reset between tests. Never rely on test execution order.
- Unit tests have zero infrastructure. No database, no file system,
no HTTP calls, no environment variables in unit tests. If you need them, it's an integration test.
Test Project Structure
solution/
├── src/
│ ├── Domain/
│ ├── Application/
│ ├── Infrastructure/
│ └── API/
└── tests/
├── Domain.UnitTests/ # Pure domain logic — no mocks needed usually
├── Application.UnitTests/ # Handlers, validators — mock output ports
├── Infrastructure.IntegrationTests/ # Repositories against real DB
└── API.IntegrationTests/ # Full HTTP stack via WebApplicationFactory
Each test project mirrors the production project it covers. Test class names: {ClassUnderTest}Tests.cs Test method names: {MethodName}_{Scenario}_{ExpectedResult}
AAA Pattern — Strict Enforcement
[Fact]
public async Task Handle_WhenProductIsOutOfStock_ShouldThrowDomainException()
{
// Arrange
var productId = Guid.NewGuid();
var mockInventory = new Mock();
mockInventory
.Setup(x => x.GetStockAsync(productId))
.ReturnsAsync(0);
var handler = new CreateOrderCommandHandler(mockInventory.Object);
var command = new CreateOrderCommand(productId, quantity: 5);
// Act
Func act = async () => await handler.Handle(command, CancellationToken.None);
// Assert
await act.Should()
.ThrowAsync()
.WithMessage("*out of stock*");
}
One Arrange. One Act. One Assert area. Multiple assertions on the same outcome are acceptable. Multiple Act calls are not.
What to Mock vs What Not to Mock
✅ Mock these (unmanaged/external dependencies):
- Third-party HTTP APIs (payment gateway, SMS provider, external ERP)
- Email/notification services
- Current time (IClock, IDateTimeProvider)
- Random/GUID generation when determinism matters
- Azure Blob Storage, Azure Service Bus
❌ Never mock these (test with real implementations):
- Your own repositories (test against real DB in integration tests)
- Domain services (test with real logic)
- MediatR pipeline (test handlers directly or via WebApplicationFactory)
- FluentValidation validators (test the real validator)
- EF Core DbContext (use real DB, not InMemory)
xUnit Fixtures — Shared Test Infrastructure
Class Fixture — shared within one test class
// Expensive setup shared across all tests in the class (created once)
public class OrderRepositoryTests : IClassFixture
{
private readonly DatabaseFixture _fixture;
public OrderRepositoryTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task GetById_WhenOrderExists_ReturnsOrder()
{
// Use _fixture.DbContext
}
}
Collection Fixture — shared across multiple test classes
// shared/DatabaseFixture.cs
public class DatabaseFixture : IAsyncLifetime
{
public AppDbContext DbContext { get; private set; } = null!;
private IDbContextTransaction _transaction = null!;
public async Task InitializeAsync()
{
// Start Docker container or connect to test DB
DbContext = CreateDbContext();
await DbContext.Database.MigrateAsync();
}
// Called before each test — wrap in transaction for isolation
public async Task BeginTransactionAsync()
{
_transaction = await DbContext.Database.BeginTransactionAsync();
}
// Called after each test — rollback to leave zero side effects
public async Task RollbackAsync()
{
await _transaction.RollbackAsync();
}
public async Task DisposeAsync()
{
await DbContext.DisposeAsync();
}
}
// shared/DatabaseCollection.cs
[CollectionDefinition(nameof(DatabaseCollection))]
public class DatabaseCollection : ICollectionFixture { }
// In test classes that need shared DB:
[Collection(nameof(DatabaseCollection))]
public class ProductRepositoryTests
{
private readonly DatabaseFixture _fixture;
public ProductRepositoryTests(DatabaseFixture fixture) => _fixture = fixture;
}
Database Isolation — Transaction Rollback Pattern
The safest isolation strategy: each test runs inside a transaction that rolls back.
public class OrderRepositoryIntegrationTests
: IClassFixture, IAsyncLifetime
{
private readonly DatabaseFixture _fixture;
public OrderRepositoryIntegrationTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
// Runs before each test
public async Task InitializeAsync() => await _fixture.BeginTransactionAsync();
// Runs after each test — database is clean for the next test
public async Task DisposeAsync() => await _fixture.RollbackAsync();
[Fact]
public async Task Save_ValidOrder_PersistsToDatabase()
{
// Arrange
var repository = new OrderRepository(_fixture.DbContext);
var order = Order.Create(Guid.NewGuid(), "customer-1");
// Act
await repository.SaveAsync(order);
await _fixture.DbContext.SaveChangesAsync();
// Assert
var saved = await repository.GetByIdAsync(order.Id);
saved.Should().NotBeNull();
saved!.CustomerId.Should().Be("customer-1");
// Transaction rolls back after this test — no cleanup needed
}
}
Integration Tests — WebApplicationFactory
// shared/ApiFactory.cs
public class ApiFactory : WebApplicationFactory, IAsyncLifetime
{
// Override to swap real DB with test DB
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove real DbContext registration
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions));
if (descriptor != null) services.Remove(descriptor);
// Register test DB
services.AddDbContext(options =>
options.UseSqlServer(TestConnectionString));
});
}
public async Task InitializeAsync() => await ResetDatabaseAsync();
public new async Task DisposeAsync() => await base.DisposeAsync();
}
// API test example
[Collection(nameof(ApiCollection))]
public class OrdersEndpointTests
{
private readonly HttpClient _client;
public OrdersEndpointTests(ApiFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task POST_Orders_WithValidRequest_Returns201()
{
// Arrange
var request = new { ProductId = Guid.NewGuid(), Quantity = 2 };
// Act
var response = await _client.PostAsJsonAsync("/api/v1/orders", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await response.Content.ReadFromJsonAsync();
body!.Id.Should().NotBeEmpty();
}
}
Testing MediatR Handlers
Test handlers directly — do not mock MediatR itself:
[Fact]
public async Task CreateOrderHandler_WithValidCommand_ReturnsOrderId()
{
// Arrange — use real domain objects, mock only external ports
var mockInventory = new Mock();
mockInventory.Setup(x => x.IsAvailableAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(true);
var handler = new CreateOrderCommandHandler(
_fixture.OrderRepository, // real repository — integration test
mockInventory.Object // mocked — external dependency
);
var command = new CreateOrderCommand(Guid.NewGuid(), quantity: 1);
// Act
var result = await handler.Handle(command, CancellationToken.None);
// Assert
result.OrderId.Should().NotBeEmpty();
}
Testing FluentValidation
[Fact]
public void Validate_WhenQuantityIsZero_ShouldHaveValidationError()
{
// Arrange
var validator = new CreateOrderCommandValidator();
var command = new CreateOrderCommand(Guid.NewGuid(), quantity: 0);
// Act
var result = validator.Validate(command);
// Assert
result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle(e =>
e.PropertyName == nameof(CreateOrderCommand.Quantity) &&
e.ErrorMessage.Contains("greater than"));
}
Common mistakes
- ❌
UseInMemoryDatabase()for integration tests — misses SQL constraints and FK violations - ✅ Use a real SQL Server in Docker (Testcontainers or a dedicated test instance)
- ❌
[Theory]with 20 inline data cases that all test the same code path - ✅ Group data-driven tests by behavior, not by data variation
- ❌ Static shared state between tests (
static List _orders) - ✅ Each test creates its own data; transaction rollback handles cleanup
- ❌ Mocking
IOrderRepositoryin integration tests to avoid the database - ✅ That makes it a unit test — name it and place it accordingly
- ❌
Assert.True(result != null)— no context when it fails - ✅
result.Should().NotBeNull("because a valid command must return an order")
Definition of Done
A test suite built with this skill is complete only when:
- [ ] Unit tests have zero infrastructure dependencies (no DB, no HTTP, no FS)
- [ ] Integration tests run against a real SQL Server (not InMemory)
- [ ] Each integration test leaves zero side effects (transaction rollback or reset)
- [ ] Moq is used only for external/unmanaged dependencies
- [ ] Test method names follow
{Method}_{Scenario}_{ExpectedResult}convention - [ ] FluentAssertions used throughout — no raw
Assert.Equal - [ ] All tests pass in parallel with no ordering dependency
Reference files
Load on demand:
references/testcontainers-setup.md— SQL Server in Docker for integration testsreferences/handler-testing-patterns.md— full patterns for CQRS handler test suitesreferences/coverage-thresholds.md— configuring coverage gates in Azure Pipelines
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: iamBrzDev
- Source: iamBrzDev/enterprise-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.