一键导入
dck-testing
Use when adding or fixing AHKFlowApp tests with xUnit, WebApplicationFactory, Testcontainers, or bUnit.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when adding or fixing AHKFlowApp tests with xUnit, WebApplicationFactory, Testcontainers, or bUnit.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when creating, removing, or troubleshooting AHKFlowApp git worktrees across Claude Code, Codex, or Copilot.
Use when optimizing AHKFlowApp agent workflow, worktrees, planning, verification loops, context budget, or tool usage.
Use when verifying AHKFlowApp build, tests, formatting, diagnostics, security, or readiness before commit, push, or PR.
Compact the current conversation into a handoff document for another agent to pick up.
Use to scan .NET code for performance anti-patterns across async, memory, strings, collections, LINQ, regex, and I/O.
Use to evaluate assertion depth and diversity across a test suite and flag assertion-free, shallow, or tautological tests.
| name | dck-testing |
| description | Use when adding or fixing AHKFlowApp tests with xUnit, WebApplicationFactory, Testcontainers, or bUnit. |
WebApplicationFactory test covers routing, binding, the validating use-case decorator, handler, and SQL Server persistence in one shot.MsSqlContainer from Testcontainers.MsSql. In-memory providers hide real SQL Server behavior (transactions, constraints, retry logic).Result status. Not which internal methods were called.result.IsSuccess.Should().BeTrue() not Assert.True(result.IsSuccess).tests/
AHKFlowApp.API.Tests/ # Integration tests — WebApplicationFactory + Testcontainers
AHKFlowApp.Application.Tests/ # Validator unit tests + handler unit tests
AHKFlowApp.Domain.Tests/ # Domain entity logic unit tests
AHKFlowApp.Infrastructure.Tests/ # EF Core + migration tests with Testcontainers
AHKFlowApp.UI.Blazor.Tests/ # Blazor component tests (bUnit)
// tests/AHKFlowApp.API.Tests/Collections.cs
[CollectionDefinition("WebApi")]
public sealed class WebApiCollection : ICollectionFixture<SqlContainerFixture>;
// tests/AHKFlowApp.TestUtilities/Fixtures/CustomWebApplicationFactory.cs
public sealed class CustomWebApplicationFactory(
SqlContainerFixture sqlFixture) : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSetting("Auth:UseTestProvider", "false");
builder.ConfigureServices(services =>
{
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.RemoveAll<AppDbContext>();
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(sqlFixture.ConnectionString,
sql => sql.EnableRetryOnFailure()));
});
}
}
// tests/AHKFlowApp.API.Tests/Hotstrings/HotstringsEndpointsTests.cs
[Collection("WebApi")]
public sealed class HotstringsEndpointsTests(SqlContainerFixture sqlFixture) : IDisposable
{
private readonly CustomWebApplicationFactory _factory = new(sqlFixture);
private HttpClient CreateAuthed(Guid? oid = null) =>
_factory.WithTestAuth(b => b.WithOid(oid ?? Guid.NewGuid())).CreateClient();
[Fact]
public async Task Post_CreatesAndReturns201WithLocation()
{
// Arrange
using HttpClient client = CreateAuthed();
var dto = new CreateHotstringDto("btw", "by the way");
// Act
var response = await client.PostAsJsonAsync("/api/v1/hotstrings", dto);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var result = await response.Content.ReadFromJsonAsync<HotstringDto>();
result.Should().NotBeNull();
result!.Trigger.Should().Be("btw");
}
[Fact]
public async Task Post_InvalidBody_Returns400()
{
// Arrange
using HttpClient client = CreateAuthed();
var dto = new CreateHotstringDto("", "by the way");
// Act
var response = await client.PostAsJsonAsync("/api/v1/hotstrings", dto);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task GetById_NotFound_Returns404()
{
// Arrange
using HttpClient client = CreateAuthed();
// Act
var response = await client.GetAsync("/api/v1/hotstrings/99999");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
public void Dispose() => _factory.Dispose();
}
Test the handler directly with a real (Testcontainers) database. Assert on Result status.
// tests/AHKFlowApp.Application.Tests/Commands/CreateHotstringHandlerTests.cs
public sealed class CreateHotstringHandlerTests : IAsyncLifetime
{
private readonly MsSqlContainer _mssql = new MsSqlBuilder().Build();
private AppDbContext _db = null!;
public async Task InitializeAsync()
{
await _mssql.StartAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(_mssql.GetConnectionString())
.Options;
_db = new AppDbContext(options);
await _db.Database.MigrateAsync();
}
public async Task DisposeAsync()
{
await _db.DisposeAsync();
await _mssql.DisposeAsync();
}
[Fact]
public async Task Handle_ValidCommand_ReturnsSuccess()
{
// Arrange
var handler = new CreateHotstringHandler(_db);
var command = new CreateHotstringCommand("btw", "by the way");
// Act
var result = await handler.ExecuteAsync(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Trigger.Should().Be("btw");
result.Value.Replacement.Should().Be("by the way");
}
[Fact]
public async Task Handle_DuplicateTrigger_ReturnsConflict()
{
// Arrange
var handler = new CreateHotstringHandler(_db);
var command = new CreateHotstringCommand("btw", "by the way");
await handler.ExecuteAsync(command, CancellationToken.None);
// Act
var result = await handler.ExecuteAsync(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeFalse();
result.Status.Should().Be(ResultStatus.Conflict);
}
}
Validators are pure functions — no database needed.
// tests/AHKFlowApp.Application.Tests/Commands/CreateHotstringValidatorTests.cs
public sealed class CreateHotstringValidatorTests
{
private readonly CreateHotstringValidator _validator = new();
[Fact]
public async Task Validate_ValidCommand_PassesValidation()
{
// Arrange
var command = new CreateHotstringCommand("btw", "by the way");
// Act
var result = await _validator.ValidateAsync(command);
// Assert
result.IsValid.Should().BeTrue();
}
[Theory]
[InlineData("", "by the way")]
[InlineData("btw", "")]
public async Task Validate_InvalidFields_FailsValidation(string trigger, string replacement)
{
// Arrange
var command = new CreateHotstringCommand(trigger, replacement);
// Act
var result = await _validator.ValidateAsync(command);
// Assert
result.IsValid.Should().BeFalse();
}
}
// Success
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
result.Value.Trigger.Should().Be("btw");
// Not found
result.IsSuccess.Should().BeFalse();
result.Status.Should().Be(ResultStatus.NotFound);
// Invalid (validation failure)
result.Status.Should().Be(ResultStatus.Invalid);
result.ValidationErrors.Should().NotBeEmpty();
// Conflict
result.Status.Should().Be(ResultStatus.Conflict);
Use IClassFixture<T> when multiple test classes share the same database container.
[Collection("WebApi")]
public sealed class HotstringsEndpointsTests(SqlContainerFixture sqlFixture) : IDisposable
{
// SqlContainerFixture is shared by the WebApi collection.
private readonly CustomWebApplicationFactory _factory = new(sqlFixture);
}
[Fact]
public async Task Handle_SetsCreatedAt()
{
// Arrange
var clock = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 0, 0, 0, TimeSpan.Zero));
var handler = new CreateHotstringHandler(_db, clock);
var command = new CreateHotstringCommand("btw", "by the way");
// Act
var result = await handler.ExecuteAsync(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.CreatedAt.Should().Be(clock.GetUtcNow());
}
Pattern: MethodName_Scenario_ExpectedResult
[Fact] public async Task Create_ValidRequest_Returns201() { }
[Fact] public async Task Create_EmptyTrigger_Returns400() { }
[Fact] public async Task GetById_HotstringExists_Returns200() { }
[Fact] public async Task GetById_NotFound_Returns404() { }
[Fact] public async Task Handle_DuplicateTrigger_ReturnsConflict() { }
[Fact] public async Task Validate_EmptyTrigger_FailsValidation() { }
// BAD — hides real SQL Server behavior, transactions, constraints
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// GOOD — Testcontainers with real SQL Server
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(_mssql.GetConnectionString()));
// BAD — mocking AppDbContext (you own it)
var mockDb = Substitute.For<AppDbContext>();
// GOOD — use real DbContext against Testcontainers SQL Server
var db = new AppDbContext(options);
// BAD — verifying method calls
mockCreateHotstringUseCase.Received(1)
.ExecuteAsync(Arg.Any<CreateHotstringCommand>(), Arg.Any<CancellationToken>());
// GOOD — assert on observable outcome
response.StatusCode.Should().Be(HttpStatusCode.Created);
var persisted = await db.Hotstrings.FirstOrDefaultAsync(h => h.Trigger == "btw");
persisted.Should().NotBeNull();
// BAD — wrong database for this project
private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder().Build();
// GOOD — SQL Server
private readonly MsSqlContainer _mssql = new MsSqlBuilder().Build();
| Scenario | Recommendation |
|---|---|
| Testing an API endpoint | WebApplicationFactory + MsSqlContainer integration test |
| Testing a handler | Real AppDbContext + MsSqlContainer |
| Testing a validator | Unit test — no database needed |
| Testing domain entity logic | Unit test — pure C# |
| Database-dependent tests | Testcontainers.MsSql — never in-memory |
| Time-dependent logic | FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing |
| Shared expensive fixture | IClassFixture<T> with IAsyncLifetime |
| Asserting Result outcomes | result.IsSuccess.Should().BeTrue(), result.Status.Should().Be(ResultStatus.NotFound) |
| Parameterized cases | [Theory] with [InlineData] |