# 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.
