Skip to content

feat: add TUnit.Aspire package#4819

Merged
thomhurst merged 18 commits intomainfrom
feat/tunit-aspire
Feb 15, 2026
Merged

feat: add TUnit.Aspire package#4819
thomhurst merged 18 commits intomainfrom
feat/tunit-aspire

Conversation

@thomhurst
Copy link
Owner

Summary

  • Adds TUnit.Aspire package with AspireFixture<TAppHost> that manages the full Aspire distributed app lifecycle (build, start, wait-for-resources, stop, dispose), eliminating ~50 lines of boilerplate per test project
  • Supports direct use via [ClassDataSource<AspireFixture<Projects.MyAppHost>>] or subclassing with virtual hooks (ConfigureBuilder, WaitBehavior, ResourceTimeout, ResourcesToWaitFor, WaitForResourcesAsync)
  • Includes WatchResourceLogs() for opt-in per-test resource log streaming
  • Updates the TUnit.Aspire.Starter template to use the new package instead of raw Aspire.Hosting.Testing boilerplate

Closes #4768

Test plan

  • Builds across all three TFMs (net8.0, net9.0, net10.0) with zero errors
  • dotnet pack produces valid NuGet package
  • Pipeline project builds with new Sourcy-generated reference
  • CI passes
  • Verify template compiles when instantiated with dotnet new tunit-aspire-starter

🤖 Generated with Claude Code

Adds AspireFixture<TAppHost> that manages the full Aspire app lifecycle
(build, start, wait-for-resources, stop, dispose), eliminating ~50 lines
of boilerplate per test project. Supports direct use via ClassDataSource
or subclassing with virtual configuration hooks.

Closes #4768

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nit.Aspire

Update TUnit.Aspire.Test template to reference TUnit.Aspire instead of
raw Aspire.Hosting.Testing boilerplate. Update CloudShop example's
DistributedAppFixture to subclass AspireFixture<TAppHost>, demonstrating
ConfigureBuilder, ResourceTimeout, and WaitForResourcesAsync overrides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Feb 15, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

This PR introduces a well-designed TUnit.Aspire package that provides clean lifecycle integration for Aspire distributed app testing. The implementation follows TUnit patterns, uses appropriate async patterns (ValueTask for potentially-synchronous disposal), and significantly improves developer experience by eliminating ~50 lines of boilerplate per test project.

@claude
Copy link
Contributor

claude bot commented Feb 15, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.


This PR introduces a well-designed package that significantly reduces boilerplate for Aspire testing. The implementation follows TUnit's patterns correctly:

  • Lifecycle management through and is properly implemented
  • Extensibility via virtual methods (, , etc.) provides good customization points
  • Template updates correctly demonstrate usage patterns with the new fixture
  • Code reduction is substantial (eliminates ~50 lines of boilerplate per test project)

The refactoring of the CloudShop example validates the approach in a real-world scenario, reducing the fixture from 48 to 29 lines while maintaining all functionality.

Two improvements to resource waiting:

1. Fail-fast: races each resource's ready-wait against a FailedToStart
   watcher. If a container crashes during startup, throws immediately
   with the resource name instead of hanging until timeout.

2. Timeout wrapping: catches OperationCanceledException from the timeout
   CTS and rethrows as TimeoutException listing all resource names, the
   wait behavior, and actionable suggestions (increase timeout, use
   WatchResourceLogs, check dashboard).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a resource enters FailedToStart, the error message now includes
the last 20 lines of that resource's logs (e.g., Docker pull failures,
config errors). On timeout, the message distinguishes ready vs. pending
resources and includes logs from each pending resource.

Example FailedToStart output:
  Resource 'redis' failed to start.

  --- redis logs ---
    Error: no matching manifest for linux/amd64

Example timeout output:
  Resources not ready: ['postgres']. Resources ready: ['redis']

  --- postgres logs ---
    waiting for server to start...

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ate builds

Template content projects reference TUnit.Aspire as a NuGet PackageReference,
but the package hasn't been published yet. During solution builds (CI), this
causes NU1101. The content Directory.Build.props (excluded from the template
package) now swaps to a local ProjectReference and adds the implicit usings
that would normally come from TUnit's NuGet props/targets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adding InternalsVisibleTo("TUnit.Aspire") to TUnit.Core changed the
assembly-level attributes, causing the Core_Library_Has_No_API_Changes
snapshot test to fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CloudShop integration tests have been consistently timing out at the
10-minute job limit because pulling Docker images for PostgreSQL, Redis,
and RabbitMQ from scratch on GitHub Actions runners takes too long.

- Add a pre-pull step that pulls all 5 Docker images in parallel before
  the test run, giving visibility into pull times
- Increase job timeout from 10 to 15 minutes
- Increase fixture ResourceTimeout from 2 to 5 minutes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This was referenced Feb 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: TUnit.Aspire integration package for Aspire distributed application testing

1 participant