Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)

Agent Tooling (71)

Automation workflows, orchestration references, and developer tooling guides.
agent-tooling/.agents/skills/tunit-testing/references/aspire-integration.md 6,081 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

Aspire Integration

Use these patterns for distributed application testing with TUnit.Aspire.

When To Use TUnit.Aspire

Use TUnit.Aspire when tests need a real Aspire AppHost and its orchestrated resources:

  • multiple services
  • databases, caches, brokers, and containers
  • real connection strings from the AppHost
  • end-to-end HTTP flows across process boundaries

Prefer TUnit.AspNetCore when testing a single ASP.NET Core app in isolation.

Basic Fixture Pattern

The simplest setup uses AspireFixture<TAppHost> directly as shared class data.

csharp
[ClassDataSource<AspireFixture<Projects.MyAppHost>>(Shared = SharedType.PerTestSession)]
public class ApiTests(AspireFixture<Projects.MyAppHost> fixture)
{
    [Test]
    public async Task GetWeatherForecast_ReturnsOk()
    {
        var client = fixture.CreateHttpClient("apiservice");
        var response = await client.GetAsync("/weatherforecast");

        await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
    }
}

This fixture handles:

  • build
  • start
  • wait for resources
  • stop
  • disposal

Subclass For Customization

Create a dedicated fixture subclass when you need timeouts, builder customization, or custom waiting behavior.

csharp
public class AppFixture : AspireFixture<Projects.MyAppHost>
{
    protected override TimeSpan ResourceTimeout => TimeSpan.FromMinutes(3);

    protected override void ConfigureBuilder(IDistributedApplicationTestingBuilder builder)
    {
        builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
        {
            clientBuilder.AddStandardResilienceHandler();
        });
    }
}

Shared Session Pattern

Distributed applications are expensive to start. Prefer:

  • [ClassDataSource<AppFixture>(Shared = SharedType.PerTestSession)]

This starts the AppHost once per test process and shares it across tests.

Resource Waiting

Pick the lightest waiting strategy that still matches your test needs.

Defaults:

  • AllHealthy for apps that expose useful health checks

Alternatives:

  • AllRunning when health checks are not configured or not needed
  • Named when tests depend on only a subset of resources
  • None when tests perform their own readiness logic
csharp
public class AppFixture : AspireFixture<Projects.MyAppHost>
{
    protected override ResourceWaitBehavior WaitBehavior => ResourceWaitBehavior.Named;

    protected override IEnumerable<string> ResourcesToWaitFor()
        => ["apiservice", "worker"];
}

Remove Unneeded Resources

If the AppHost defines dashboards, admin tools, or optional developer-only resources, remove them from the test app instead of waiting for them.

csharp
public class AppFixture : AspireFixture<Projects.MyAppHost>
{
    protected override IEnumerable<string> ResourcesToRemove()
        => ["pgadmin", "redisinsight", "seq"];
}

This reduces startup time and CI flakiness.

Lifecycle Hooks In Fixtures

AspireFixture<TAppHost> already implements lifecycle management, but you can extend it safely.

Use InitializeAsync() for post-start actions such as:

  • migrations
  • seeding
  • warming caches
csharp
public class AppFixture : AspireFixture<Projects.MyAppHost>
{
    public override async Task InitializeAsync()
    {
        await base.InitializeAsync();

        var connectionString = await GetConnectionStringAsync("postgresdb");
        await RunMigrationsAsync(connectionString!);
    }
}

Fixture Chains

You can build higher-level fixtures on top of an Aspire fixture using property injection.

csharp
public class ApiClientFixture : IAsyncInitializer
{
    [ClassDataSource<AppFixture>(Shared = SharedType.PerTestSession)]
    public required AppFixture App { get; init; }

    public HttpClient Client { get; private set; } = null!;

    public Task InitializeAsync()
    {
        Client = App.CreateHttpClient("apiservice");
        return Task.CompletedTask;
    }
}

This is useful for:

  • preconfigured HTTP clients
  • direct database connections
  • cache clients
  • message broker clients

Telemetry And Log Correlation

AspireFixture can correlate SUT logs and traces back to the test that triggered the request.

Guidance:

  • prefer fixture.CreateHttpClient(...) instead of constructing clients manually
  • leave telemetry collection enabled unless you have a specific reason to disable it
  • use WatchResourceLogs(resourceName) when debugging startup/resource failures
csharp
[Test]
public async Task Create_Order_Returns_201()
{
    await using var _ = fixture.WatchResourceLogs("apiservice");

    var client = fixture.CreateHttpClient("apiservice");
    var response = await client.PostAsJsonAsync("/api/orders", new { Item = "Widget" });

    await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.Created);
}

AppHost Arguments vs Builder Configuration

Use the right extension point for the right phase:

  • Args for values the AppHost reads during builder creation
  • ConfigureAppHost(...) for builder creation options and host settings
  • ConfigureBuilder(...) for services and post-builder customization

If the AppHost reads a value during CreateBuilder(args), passing it later in ConfigureBuilder(...) is too late.

CI Guidance

Common CI requirements:

  • Docker must be available
  • use SharedType.PerTestSession
  • increase ResourceTimeout for slower runners
  • if HTTPS dev cert trust is an issue, set ASPIRE_ALLOW_UNSECURED_TRANSPORT=true

Diagnostics

When startup or readiness fails:

  • inspect recent resource logs in the thrown timeout/fail-fast diagnostics
  • use WatchResourceLogs(...) in focused tests
  • reduce the surface area by removing unneeded resources
  • switch from AllHealthy to AllRunning if health checks are the blocker and not required by the test

Decision Rule

Use:

  • TUnit.Aspire for full distributed app tests around an Aspire AppHost
  • TUnit.AspNetCore for isolated in-process ASP.NET Core integration tests

They can coexist in the same solution when different test layers need different tradeoffs.