diff --git a/src/frontend/src/content/docs/testing/manage-app-host.mdx b/src/frontend/src/content/docs/testing/manage-app-host.mdx
index 70e6471d..2ae26db7 100644
--- a/src/frontend/src/content/docs/testing/manage-app-host.mdx
+++ b/src/frontend/src/content/docs/testing/manage-app-host.mdx
@@ -14,6 +14,7 @@ import Pivot from '@components/Pivot.astro';
{ id: "xunit", title: "xUnit.net" },
{ id: "mstest", title: "MSTest" },
{ id: "nunit", title: "NUnit" },
+ { id: "tunit", title: "TUnit" },
]}
/>
@@ -120,6 +121,36 @@ public class WebTests
}
```
+
+
+
+With TUnit, you use the hooks [Before](https://tunit.dev/docs/test-lifecycle/setup) and [After](https://tunit.dev/docs/test-lifecycle/cleanup) attributes on methods of the test class to provide the setup and cleanup of the AppHost instance. The `Setup` method is used to create the AppHost instance before the tests are run and the `Cleanup` method disposes the AppHost instance once the tests are completed.
+
+```csharp
+public class WebTests
+{
+ private DistributedApplication _app;
+
+ [Before(Test)]
+ public async Task Setup()
+ {
+ var appHost = await DistributedApplicationTestingBuilder
+ .CreateAsync();
+
+ _app = await appHost.BuildAsync();
+ }
+
+ [After(Test)]
+ public async Task Cleanup() => await _app.DisposeAsync();
+
+ [Test]
+ public async Task GetWebResourceRootReturnsOkStatusCode()
+ {
+ // test code here
+ }
+}
+```
+
By capturing the AppHost in a field when the test run is started, you can access it in each test without the need to recreate it, decreasing the time it takes to run the tests. Then, when the test run completes, the AppHost is disposed, which cleans up any resources that were created during the test run, such as containers.
diff --git a/src/frontend/src/content/docs/testing/write-your-first-test.mdx b/src/frontend/src/content/docs/testing/write-your-first-test.mdx
index 30df2b48..140a9d4f 100644
--- a/src/frontend/src/content/docs/testing/write-your-first-test.mdx
+++ b/src/frontend/src/content/docs/testing/write-your-first-test.mdx
@@ -15,6 +15,7 @@ import Pivot from '@components/Pivot.astro';
{ id: "xunit", title: "xUnit.net" },
{ id: "mstest", title: "MSTest" },
{ id: "nunit", title: "NUnit" },
+ { id: "tunit", title: "TUnit" },
]}
/>
@@ -50,6 +51,13 @@ dotnet new aspire-mstest -o MSTest.Tests
dotnet new aspire-nunit -o NUnit.Tests
```
+
+
+
+```bash
+dotnet new aspire-tunit -o TUnit.Tests
+```
+
Change directory to the newly created test project:
@@ -74,6 +82,13 @@ cd MSTest.Tests
cd NUnit.Tests
```
+
+
+
+```bash
+cd TUnit.Tests
+```
+
After adding the test project to your Aspire solution, add a project reference to the target AppHost. For example, if your Aspire solution contains an AppHost project named `AspireApp.AppHost`, add a project reference to it from the test project:
@@ -98,6 +113,13 @@ dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project MST
dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project NUnit.Tests.csproj
```
+
+
+
+```bash
+dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project TUnit.Tests.csproj
+```
+
Finally, you can uncomment out the `IntegrationTest1.cs` file in the test project to explore the sample test.
@@ -216,6 +238,39 @@ The following example test project was created as part of the **Blazor & Minimal
```
+
+
+
+```xml title="TUnit.Tests.csproj"
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
The preceding project file is fairly standard. There's a `PackageReference` to the [📦 Aspire.Hosting.Testing](https://www.nuget.org/packages/Aspire.Hosting.Testing) NuGet package, which includes the required types to write tests for Aspire projects.
@@ -378,6 +433,56 @@ public class IntegrationTest1
}
```
+
+
+
+```csharp title="IntegrationTest1.cs"
+using Microsoft.Extensions.Logging;
+
+namespace TUnit.Tests;
+
+public class IntegrationTest1
+{
+ private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
+
+ [Test]
+ public async Task GetWebResourceRootReturnsOkStatusCode()
+ {
+ // Arrange
+ using var cts = new CancellationTokenSource(DefaultTimeout);
+ var cancellationToken = cts.Token;
+ var appHost = await DistributedApplicationTestingBuilder
+ .CreateAsync();
+ appHost.Services.AddLogging(logging =>
+ {
+ logging.SetMinimumLevel(LogLevel.Debug);
+ // Override the logging filters from the app's configuration
+ logging.AddFilter(appHost.Environment.ApplicationName, LogLevel.Debug);
+ logging.AddFilter("Aspire.", LogLevel.Debug);
+ });
+ appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
+ {
+ clientBuilder.AddStandardResilienceHandler();
+ });
+
+ await using var app = await appHost.BuildAsync(cancellationToken)
+ .WaitAsync(DefaultTimeout, cancellationToken);
+ await app.StartAsync(cancellationToken)
+ .WaitAsync(DefaultTimeout, cancellationToken);
+
+ // Act
+ using var httpClient = app.CreateHttpClient("webfrontend");
+ await app.ResourceNotifications.WaitForResourceHealthyAsync(
+ "webfrontend", cancellationToken)
+ .WaitAsync(DefaultTimeout, cancellationToken);
+ using var response = await httpClient.GetAsync("/", cancellationToken);
+
+ // Assert
+ await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
+ }
+}
+```
+
The preceding code:
@@ -495,6 +600,36 @@ public class EnvVarTests
}
```
+
+
+
+```csharp title="EnvVarTests.cs"
+using Aspire.Hosting;
+
+namespace Tests;
+
+public class EnvVarTests
+{
+ [Test]
+ public async Task WebResourceEnvVarsResolveToApiService()
+ {
+ // Arrange
+ var builder = await DistributedApplicationTestingBuilder
+ .CreateAsync();
+
+ var frontend = builder.CreateResourceBuilder("webfrontend");
+
+ // Act
+ var envVars = await frontend.Resource.GetEnvironmentVariableValuesAsync(
+ DistributedApplicationOperation.Publish);
+
+ // Assert
+ await Assert.That(envVars)
+ .Contains(new KeyValuePair("APISERVICE_HTTPS", "{apiservice.bindings.https.url}"));
+ }
+}
+```
+
The preceding code:
@@ -660,6 +795,55 @@ public class LoggingTest
}
```
+
+
+
+```csharp title="LoggingTest.cs"
+using Microsoft.Extensions.Logging;
+
+namespace Tests;
+
+public class LoggingTest
+{
+ [Test]
+ public async Task GetWebResourceRootReturnsOkStatusCodeWithLogging()
+ {
+ // Arrange
+ var builder = await DistributedApplicationTestingBuilder
+ .CreateAsync();
+
+ builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
+ {
+ clientBuilder.AddStandardResilienceHandler();
+ });
+
+ // Configure logging to capture test execution logs
+ builder.Services.AddLogging(logging => logging
+ .AddConsole() // Outputs logs to console
+ .AddFilter("Default", LogLevel.Information)
+ .AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
+ .AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));
+
+ await using var app = await builder.BuildAsync();
+
+ await app.StartAsync();
+
+ // Act
+ var httpClient = app.CreateHttpClient("webfrontend");
+
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+ await app.ResourceNotifications.WaitForResourceHealthyAsync(
+ "webfrontend",
+ cts.Token);
+
+ var response = await httpClient.GetAsync("/");
+
+ // Assert
+ await Assert.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK);
+ }
+}
+```
+
### Configure log filters
@@ -711,6 +895,15 @@ For NUnit, consider using one of these logging packages:
- [📦 Serilog.Extensions.Logging.File](https://www.nuget.org/packages/Serilog.Extensions.Logging.File) - Writes logs to files.
- [📦 Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) - Outputs logs to console.
+
+
+
+For TUnit, consider using one of these logging packages:
+
+- [📦 Dameng.Logging.TUnit](https://www.nuget.org/packages/Dameng.Logging.TUnit) - Provides an `ILogger` implementation seamlessly integrated with TUnit's `TestContext.Current.OutputWriter`.
+- [📦 Serilog.Extensions.Logging.File](https://www.nuget.org/packages/Serilog.Extensions.Logging.File) - Writes logs to files.
+- [📦 Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console) - Outputs logs to console.
+
## Summary