Tracon.Testing.Contracts.Xunit 1.0.0-preview.3

Prefix Reserved
This is a prerelease version of Tracon.Testing.Contracts.Xunit.
dotnet add package Tracon.Testing.Contracts.Xunit --version 1.0.0-preview.3
                    
NuGet\Install-Package Tracon.Testing.Contracts.Xunit -Version 1.0.0-preview.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tracon.Testing.Contracts.Xunit" Version="1.0.0-preview.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tracon.Testing.Contracts.Xunit" Version="1.0.0-preview.3" />
                    
Directory.Packages.props
<PackageReference Include="Tracon.Testing.Contracts.Xunit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tracon.Testing.Contracts.Xunit --version 1.0.0-preview.3
                    
#r "nuget: Tracon.Testing.Contracts.Xunit, 1.0.0-preview.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tracon.Testing.Contracts.Xunit@1.0.0-preview.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tracon.Testing.Contracts.Xunit&version=1.0.0-preview.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Tracon.Testing.Contracts.Xunit&version=1.0.0-preview.3&prerelease
                    
Install as a Cake Tool

Tracon.Testing.Contracts.Xunit

Behavior contract suites for Tracon's extension points — the store interfaces (IRunStore and 29 others), IModelProvider, IRunJudge, IAgentSource, IJobHandler, and code-defined custom tools — packaged as xunit.v3 test base classes.

Tracon ships four store implementations (in-memory, PostgreSQL, SQL Server, SQLite) and they all pass the same tests — the base classes in this package. If you write your own store (a fifth SQL dialect, a document database, a hand-rolled adapter over an existing system), derive one contract class per interface you implement and inherit the same scenarios.

The same applies to a model provider or run judge Tracon ships no package for. Most of what IModelProvider requires cannot be checked by a compiler — returning an already-wrapped chat client, capturing a scoped service, rejecting a model the catalog does not list — so deriving ModelProviderContract is how you find out before a deployment does.

Install

dotnet add package Tracon.Testing.Contracts.Xunit --prerelease

The package name says what it needs: xunit.v3 and Shouldly are ordinary dependencies, not private ones — a consuming test project's runner discovers and runs the [Fact] methods the contract classes inherit. There is no NUnit or MSTest edition; a project on either framework cannot use this package.

Use

using Tracon.Testing.Contracts.Storage;

public sealed class MyRunStoreTests : RunStoreContract
{
    protected override ValueTask<IRunStore> CreateStoreAsync()
        => new(new MyRunStore());
}

dotnet test then runs every scenario RunStoreContract defines — idempotent StartRunAsync, tenant isolation (both directions), event ordering, statistics aggregation, and more — against your implementation. A method your store does not implement makes the inherited test fail with your own exception; it does not fail to compile.

What is covered

Stores — Tracon.Testing.Contracts.Storage

One abstract class per Tracon.Abstractions store interface: RunStoreContract, SessionStoreContract, AgentDefinitionStoreContract, ExperimentStoreContract, and 27 more. Every one derives from TenantIsolationContract<TStore>, which supplies the shared lifecycle plumbing (InitializeAsync/DisposeAsync) and the two-directional tenant check every store must pass: a tenant reads its own records, and never another tenant's.

TestData supplies ready-made sample records (TestData.Run(...), TestData.Session(...), and so on) for writing additional scenarios beside the inherited ones.

Model providers — Tracon.Testing.Contracts.Providers

ModelProviderContract is what every provider owes. Fill in one member:

using Tracon.Testing.Contracts.Providers;

public sealed class ContosoProviderTests : ModelProviderContract
{
    protected override ValueTask<IModelProvider> CreateProviderAsync()
        => new(new ContosoModelProvider("any-key"));
}

It asserts that the provider returns a raw chat client (the tool-call loop, telemetry, the content guard and the circuit breaker belong to Tracon, and building them inside a provider hides the tool-result turn from the guard), that CreateChatClient survives concurrent calls, that a model absent from the catalog is not rejected, that a binding whose provider name differs in casing still works, and that a client nobody disposes does not break the provider.

Two more classes cover behavior that is a provider's choice. Derive them only if you offer it — deriving is the statement of intent, which is why no scenario in this package silently skips:

Class Derive it when
ModelProviderCredentialContract The provider honors a per-tenant credential (BYOK)
ModelProviderSettingsContract The provider reads ModelBinding.ProviderSettings

ModelProviderCredentialContract also requires AssertCredentialIsApplied(IChatClient, ModelProviderCredential). Make that assertion observe the provider request boundary, such as a recording transport or SDK request factory. A different client object is not enough proof: it can still send the setup-time key and bill the wrong tenant.

Run judges — Tracon.Testing.Contracts.Judges

Derive RunJudgeContract for a judge that scores completed runs. It checks the stable metric-safe name, the 0–100 score boundary, large Reason values, repeat calls, concurrent calls, and a pre-cancelled token. The suite does not require a judge to use cancellation because deterministic judges need no I/O.

using Tracon.Testing.Contracts.Judges;

public sealed class ResponseQualityJudgeTests : RunJudgeContract
{
    protected override ValueTask<IRunJudge> CreateJudgeAsync()
        => new(new ResponseQualityJudge());
}

Agent sources — Tracon.Testing.Contracts.AgentSources

Derive AgentSourceContract for an IAgentSource. It checks stable source metadata, repeat and concurrent list/resolve behavior, listed-name consistency, and cancellation. Derive VersionedAgentSourceContract only when the source implements IVersionedAgentSource. Derive TenantAwareAgentSourceContract only when it changes its list for the ambient tenant.

Custom tools — Tracon.Testing.Contracts.Tools

Derive CustomToolContract for a code-defined tool registration. It checks the registration's declared shape against what the tool actually returns when the runtime invokes it. Derive RepeatableToolContract instead — it extends CustomToolContract — only when the tool sets TraconToolRegistration.SafeToRepeat, because that flag is a promise the runtime acts on and the extra scenarios are what hold you to it.

Two more classes test your own implementations of Tracon's two tool gates, not a tool itself:

using Tracon.Testing.Contracts.Tools;
using Microsoft.Extensions.AI;

public sealed class MyValidatorTests : ToolArgumentValidationContract
{
    protected override ValueTask<AIFunction> CreateToolAsync() => new(MyTools.Search);

    protected override ValueTask<IToolArgumentsValidator> CreateValidatorAsync() => new(new MyValidator());
}

ToolArgumentValidationContract reads Tool's own JSON Schema and fuzzes Validator against it: a missing required field, a type mismatch, an out-of-range number, a pattern violation, an unrecognized extra property, and a poisoned call most likely to fault a naive validator's own code. Every scenario accepts either a rejection or a thrown exception — Tracon's own wrapper turns a thrown exception into a rejection (fail-closed), so both count as the call being turned away. A scenario your tool's schema does not exercise (no numeric bound, no pattern, a nested-object parameter this generator does not model) is skipped with an explicit reason, not silently passed. Override ExtraPropertyIsRejected to state whether your validator accepts an undeclared property — Tracon does not mandate either way.

ToolAuthorizationContract tests your IToolAuthorizationHandler the same way CustomToolContract tests a tool: you supply the ground truth. Authorization is your own business policy — there is no schema to derive an "invalid" call from — so you declare DeniedRequest and AllowedRequest, two calls your handler's own policy decides oppositely. The contract checks the handler actually reaches that denial (by returning it directly, or by throwing and relying on Tracon's fail-closed wrapper), never runs the protected call once denied, and genuinely bases its decision on the request it was given — a handler that returns the same verdict regardless of who is calling cannot pass by ignoring its input.

Job handlers — Tracon.Testing.Contracts.Scheduling

Derive JobHandlerContract for an IJobHandler. Job delivery is at-least-once: a lease can expire and hand the same items to your handler again. The contract holds you to the two behaviors this requires — a handler must not reprocess an item it already reported, and it must observe cancellation between items.

using Tracon.Testing.Contracts.Scheduling;

public sealed class NightlyReportHandlerTests : JobHandlerContract
{
    protected override ValueTask<IJobHandler> CreateHandlerAsync()
        => new(new NightlyReportHandler());

    protected override JobItemRecord CreateItem(int sequence, JobItemStatus status)
        => new()
        {
            Id = Guid.NewGuid(), JobId = JobId, Seq = sequence,
            Input = $"customer-{sequence}", Status = status,
        };
}

Checking you derived them all

ContractCoverage reports contract classes your test assembly has no derived type for, so a class added in a later release does not silently go uncovered. Every call names one family:

using Tracon.Testing.Contracts;

[Fact]
public void Every_provider_contract_has_a_derived_test()
    => ContractCoverage.MissingDerivedTypes(
        Assembly.GetExecutingAssembly(), ContractCoverage.ProviderContracts).ShouldBeEmpty();

One constant per family: ContractCoverage.StorageContracts, ProviderContracts, JudgeContracts, AgentSourceContracts, ToolContracts, and SchedulingContracts. A contract you deliberately do not implement goes in the except argument, where a name that matches nothing is itself reported — a stale exemption must not pass quietly.

See also

  • Write your own store — a worked example, using this package against a from-scratch IRunStore.
  • IRunStore reference — the largest and most-documented contract; start there if you are writing a custom run store.
  • Model providers — the runtime contract ModelProviderContract checks, stated in prose.
  • IModelProvider reference — the full contract: lifetime, threading, naming, catalog semantics, failure classification, and who owns the returned client.

Licence: MIT - deliberately permissive, so that proving your own implementation correct never needs a commercial licence. It depends only on Tracon.Abstractions, which is MIT for the same reason. Most Tracon packages are PolyForm Small Business 1.0.0. Details: https://tracon.dev/reference/licensing/

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-preview.3 40 9/24/2026
1.0.0-preview.2 75 9/20/2026
1.0.0-preview.1 60 9/20/2026