feat: Add Aspire AppHost testing.#1
Conversation
There was a problem hiding this comment.
Pull request overview
Adds initial infrastructure to run Aspire AppHost-related tests and wires them into CI for PR validation.
Changes:
- Adds a new
DistHaul.TestsxUnit project configured for Microsoft.Testing.Platform. - Updates the Aspire AppHost project and Aspire config to align with the new
DistHaul.AppHostlocation/name. - Extends the GitHub Actions workflow to run .NET restore/build/test as part of the end-to-end job (including on pull requests).
Reviewed changes
Copilot reviewed 8 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
global.json |
Configures dotnet test runner selection (Microsoft.Testing.Platform). |
DistHaul/DistHaul.Tests/xunit.runner.json |
Adds xUnit runner schema file for the new test project. |
DistHaul/DistHaul.Tests/UnitTest1.cs |
Adds a placeholder test class/test. |
DistHaul/DistHaul.Tests/DistHaul.Tests.csproj |
Introduces a new test project referencing the AppHost and MTP-enabled xUnit packages. |
DistHaul/DistHaul.sln |
Adds the new test project and renames/relocates the AppHost entry. |
DistHaul/DistHaul.AppHost/Properties/launchSettings.json |
Adds launch profiles for the AppHost. |
DistHaul/DistHaul.AppHost/Program.cs |
Introduces the Aspire distributed application composition (Keycloak/Kafka/Redis/Postgres + executables). |
DistHaul/DistHaul.AppHost/DistHaul.AppHost.csproj |
Enables warnings-as-errors and adds Aspire.Hosting.Testing. |
DistHaul/.gitignore |
Adds a standard .NET-focused gitignore for the DistHaul/ subtree. |
aspire.config.json |
Updates the configured AppHost path to DistHaul/DistHaul.AppHost. |
.github/workflows/main.yml |
Runs the new AppHost restore/build/test steps and triggers on PRs to main. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| working-directory: DistHaul | ||
|
|
||
| - name: Build Aspire AppHost | ||
| run: dotnet build DistHaul.sln --confiugration Release --no-restore |
There was a problem hiding this comment.
The dotnet build command has a misspelled option (--confiugration), which will cause this step to fail. Use the correct --configuration option name.
| run: dotnet build DistHaul.sln --confiugration Release --no-restore | |
| run: dotnet build DistHaul.sln --configuration Release --no-restore |
| working-directory: DistHaul | ||
|
|
||
| - name: Build Aspire AppHost | ||
| run: dotnet build DistHaul.sln --confiugration Release --no-restore |
There was a problem hiding this comment.
This dotnet build DistHaul.sln step runs from the repo root (no working-directory), but DistHaul.sln appears to live under the DistHaul/ folder (as used in the restore step). Either set working-directory: DistHaul here or update the command to build DistHaul/DistHaul.sln.
| run: dotnet build DistHaul.sln --confiugration Release --no-restore | |
| run: dotnet build DistHaul.sln --confiugration Release --no-restore | |
| working-directory: DistHaul |
| run: dotnet build DistHaul.sln --confiugration Release --no-restore | ||
|
|
||
| - name: Execute Aspire AppHost Tests | ||
| run: dotnet test --solution .\DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory .\TestResults --coverage --coverage-output-format cobertura |
There was a problem hiding this comment.
dotnet test doesn’t support a --solution option; the solution or project should be passed as a positional argument (e.g., dotnet test DistHaul.sln). As written, this will fail argument parsing.
| run: dotnet test --solution .\DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory .\TestResults --coverage --coverage-output-format cobertura | |
| run: dotnet test DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory ./TestResults --coverage --coverage-output-format cobertura |
| run: dotnet build DistHaul.sln --confiugration Release --no-restore | ||
|
|
||
| - name: Execute Aspire AppHost Tests | ||
| run: dotnet test --solution .\DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory .\TestResults --coverage --coverage-output-format cobertura |
There was a problem hiding this comment.
This job uses shell: bash but passes Windows-style paths (.\DistHaul.sln, .\TestResults). In bash, backslashes will be treated as escapes and likely resolve to the wrong paths (or not resolve at all). Use ./DistHaul.sln and ./TestResults (or just DistHaul.sln / TestResults) to be portable on ubuntu-latest.
| run: dotnet test --solution .\DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory .\TestResults --coverage --coverage-output-format cobertura | |
| run: dotnet test --solution ./DistHaul.sln --configuration Release --no-build --verbosity normal --results-directory ./TestResults --coverage --coverage-output-format cobertura |
| namespace DistHaul.Tests; | ||
|
|
||
| public class UnitTest1 | ||
| { | ||
| [Fact] | ||
| public void Test1() | ||
| { | ||
| Assert.True(true); | ||
| } |
There was a problem hiding this comment.
Assert.True(true) is a no-op test and won’t catch regressions. Replace this with a meaningful test that exercises the Aspire AppHost (e.g., using Aspire.Hosting.Testing to start the distributed app and assert expected resources/endpoints are healthy).
| namespace DistHaul.Tests; | |
| public class UnitTest1 | |
| { | |
| [Fact] | |
| public void Test1() | |
| { | |
| Assert.True(true); | |
| } | |
| using System.Reflection; | |
| using Aspire.Hosting.Testing; | |
| namespace DistHaul.Tests; | |
| public class UnitTest1 | |
| { | |
| [Fact] | |
| public async Task Test1() | |
| { | |
| var appHostType = FindAppHostType(); | |
| Assert.NotNull(appHostType); | |
| var builder = await CreateTestingBuilderAsync(appHostType!); | |
| Assert.NotNull(builder); | |
| var buildAsyncMethod = builder.GetType().GetMethod("BuildAsync", Type.EmptyTypes); | |
| Assert.NotNull(buildAsyncMethod); | |
| var buildTask = (Task)buildAsyncMethod!.Invoke(builder, null)!; | |
| await buildTask; | |
| var app = buildTask.GetType().GetProperty("Result")!.GetValue(buildTask); | |
| Assert.NotNull(app); | |
| try | |
| { | |
| var startAsyncMethod = app!.GetType().GetMethod("StartAsync", Type.EmptyTypes); | |
| Assert.NotNull(startAsyncMethod); | |
| var startTask = (Task)startAsyncMethod!.Invoke(app, null)!; | |
| await startTask; | |
| } | |
| finally | |
| { | |
| if (app is IAsyncDisposable asyncDisposable) | |
| { | |
| await asyncDisposable.DisposeAsync(); | |
| } | |
| else if (app is IDisposable disposable) | |
| { | |
| disposable.Dispose(); | |
| } | |
| } | |
| } | |
| private static Type? FindAppHostType() | |
| { | |
| foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) | |
| { | |
| Type? appHostType; | |
| try | |
| { | |
| appHostType = assembly | |
| .GetTypes() | |
| .FirstOrDefault(t => | |
| t is { IsClass: true, IsAbstract: false } && | |
| t.Namespace == "Projects" && | |
| t.Name.EndsWith("_AppHost", StringComparison.Ordinal)); | |
| } | |
| catch (ReflectionTypeLoadException ex) | |
| { | |
| appHostType = ex.Types.FirstOrDefault(t => | |
| t is { IsClass: true, IsAbstract: false } && | |
| t.Namespace == "Projects" && | |
| t.Name.EndsWith("_AppHost", StringComparison.Ordinal)); | |
| } | |
| if (appHostType is not null) | |
| { | |
| return appHostType; | |
| } | |
| } | |
| return null; | |
| } | |
| private static async Task<object> CreateTestingBuilderAsync(Type appHostType) | |
| { | |
| var createAsyncMethod = typeof(DistributedApplicationTestingBuilder) | |
| .GetMethods(BindingFlags.Public | BindingFlags.Static) | |
| .First(m => m.Name == "CreateAsync" && m.IsGenericMethodDefinition && m.GetParameters().Length == 0) | |
| .MakeGenericMethod(appHostType); | |
| var createTask = (Task)createAsyncMethod.Invoke(null, null)!; | |
| await createTask; | |
| return createTask.GetType().GetProperty("Result")!.GetValue(createTask)!; | |
| } |
Not sure how useful this is tbh, but let's see where is takes us.
No description provided.