This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Detester is a .NET library (NuGet package Detester) for writing deterministic, reliable tests against AI applications. It wraps any Microsoft.Extensions.AI IChatClient in a fluent builder that sends prompts and asserts on the responses.
Targets .NET 10 only. Uses central package management — all package versions live in Directory.Packages.props, never in .csproj files. Add packages with <PackageReference Include="X" /> (no Version) and add the version to Directory.Packages.props.
dotnet restore
dotnet build --no-restore
# Unit tests — these projects are OutputType=Exe (xUnit v3 / Microsoft Testing Platform).
# CI runs them via `dotnet run`, NOT `dotnet test`:
dotnet run --project test/Detester.Tests/Detester.Tests.csproj
# Integration tests (live Azure OpenAI — see env vars below):
dotnet run --project test/Detester.IntegrationTests/Detester.IntegrationTests.csprojRun a single test (Microsoft Testing Platform filter syntax):
dotnet run --project test/Detester.Tests/Detester.Tests.csproj -- --filter-method "Detester.Tests.DetesterBuilderTests.MethodName"dotnet test --filter "FullyQualifiedName~Name" also works locally, but CI uses the dotnet run form above — match it when reproducing CI failures.
src/Detesterbuilds withTreatWarningsAsErrors=trueandStyleCop.Analyzersis a global analyzer, so StyleCop violations fail the build. Public APIs require XML doc comments.LangVersionispreview.- The two test projects do not treat warnings as errors.
AzureOpenAIChatClientFixture throws on construction if these are not set, so the entire integration suite fails fast without them:
AzureOpenAI__ApiKeyAzureOpenAI__EndpointAzureOpenAI__ChatDeploymentName
Single shipping project: src/Detester. There is no separate assembly for abstractions — interfaces/types live in src/Detester/Abstractions/ under the namespace Detester.Abstraction (note: singular), while the implementation namespace is Detester.
Core flow:
DetesterFactory.Create(IChatClient)ornew DetesterBuilder(IChatClient[, ChatOptions])→ anIDetesterBuilder.- All
WithPrompt/Should*calls only accumulate state on the builder (lists of prompts and expectations). Nothing executes until anAssert*call. DoAssertAsync(inDetesterBuilder.cs) is the single execution path. It builds one growingChatMessageconversation (optional system instruction + each prompt + each assistant reply appended), and sends every prompt sequentially through the sameIChatClient.- Every accumulated assertion is checked against every prompt's response, not pairwise. Keep this in mind when adding assertion types or reasoning about multi-prompt tests.
- Assertions are stored as small expectation records:
EqualityExpectation,FunctionCallExpectation,JsonExpectation.ReliabilityResultis returned byAssertReliablyAsync. AssertReliablyAsync(runs, requiredPassRate)simply loopsDoAssertAsync, countingDetesterExceptions as failures.- Failures throw
DetesterException; misuse of the fluent API (e.g.OrShouldContainResponsewith no prior assertion) throwsInvalidOperationException/ArgumentException.
Unit tests use MockChatClient / CallbackMockChatClient (in test/Detester.Tests/) — extend these rather than introducing a new mock when adding coverage.
- New builder methods: declare on
IDetesterBuilderfirst with XML docs, implement onDetesterBuilderreturningthis, validate inputs early (ArgumentExceptionfor bad values,ArgumentNullExceptionfor nulls), and add an expectation record if it needs per-response evaluation inDoAssertAsync. - Prefer
Microsoft.Extensions.AIabstractions over direct provider SDKs insrc/Detester. - Update
README.mdfor user-facing API changes; update the package<Version>insrc/Detester/Detester.csprojandDirectory.Packages.propsfor releases.
.github/copilot-instructions.md is partially out of date: it references a separate Detester.Abstraction project and factory methods (CreateWithOpenAI, CreateWithAzureOpenAI, Create(options), DetesterOptions) that do not exist in the codebase. Trust the actual source over that file.