Add testing docs: advanced scenarios and CI/CD patterns#456
Add testing docs: advanced scenarios and CI/CD patterns#456
Conversation
Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
IEvangelist
left a comment
There was a problem hiding this comment.
This PR looks good so far, but let's double check it for accuracy by using the .github/skills/doc-tester/SKILL.md skill. Please also consider all new comments and feedback. @copilot
| | `AZURE_TENANT_ID` | Your Azure Active Directory tenant ID | | ||
| | `AZURE_CLIENT_SECRET` | The client secret of your service principal | | ||
|
|
||
| <Tabs> |
There was a problem hiding this comment.
Please always use a syncKey on all Tabs.
There was a problem hiding this comment.
Added syncKey="ci-provider" to the <Tabs> component in da2ffdb.
|
|
||
| ### Use `DefaultAzureCredential` with environment variables | ||
|
|
||
| The recommended approach is to configure a service principal and set the standard Azure SDK environment variables in your CI pipeline. `DefaultAzureCredential` automatically picks up these environment variables: |
There was a problem hiding this comment.
Do we need to link to the new content that addressed this issue #424?
There was a problem hiding this comment.
Added an <Aside type="note"> linking to DefaultAzureCredential behavior in Azure deployments (from PR #424) in da2ffdb. This surfaces the related AZURE_TOKEN_CREDENTIALS=managedidentity change for readers who may encounter it when their tests exercise deployment paths.
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: '9.x' |
There was a problem hiding this comment.
| dotnet-version: '9.x' | |
| dotnet-version: '10.x' |
Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
Ran doc-tester validation on both new pages. Two issues were found and fixed in da2ffdb:
|
There was a problem hiding this comment.
Pull request overview
Adds new Testing documentation pages to cover advanced DistributedApplicationTestingBuilder scenarios and CI/CD pipeline patterns, and wires them into the docs sidebar.
Changes:
- Added a new “Advanced testing scenarios” page covering optional/conditional resources, wait annotation removal, env/config overrides, and
DistributedApplicationFactoryusage. - Added a new “Testing in CI/CD pipelines” page covering container runtime needs, timeout patterns, Azure auth in CI, parallelism/ports, and a GitHub Actions workflow example.
- Updated the Testing sidebar section to include both new pages.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/frontend/src/content/docs/testing/testing-in-ci.mdx | New CI/CD-focused testing guidance, examples, and troubleshooting. |
| src/frontend/src/content/docs/testing/advanced-scenarios.mdx | New advanced testing patterns for Aspire integration tests. |
| src/frontend/config/sidebar/docs.topics.ts | Adds the two new pages to the Testing section navigation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Read credentials from environment and forward to AppHost configuration | ||
| if (Environment.GetEnvironmentVariable("AZURE_CLIENT_ID") is { } clientId) | ||
| { | ||
| hostOptions.Configuration["AZURE_CLIENT_ID"] = clientId; | ||
| } | ||
|
|
||
| if (Environment.GetEnvironmentVariable("AZURE_TENANT_ID") is { } tenantId) | ||
| { | ||
| hostOptions.Configuration["AZURE_TENANT_ID"] = tenantId; | ||
| } | ||
|
|
||
| if (Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID") is { } subscriptionId) | ||
| { | ||
| hostOptions.Configuration["AZURE_SUBSCRIPTION_ID"] = subscriptionId; | ||
| } |
There was a problem hiding this comment.
This factory example forwards AZURE_CLIENT_ID/AZURE_TENANT_ID but omits AZURE_CLIENT_SECRET, even though the earlier section lists it as required for service principal auth. Add forwarding for AZURE_CLIENT_SECRET (and keep the set of forwarded variables consistent with the guidance above).
There was a problem hiding this comment.
Added AZURE_CLIENT_SECRET forwarding to the CiAppHostFactory example in c90d20c.
| ```csharp title="C# — IntegrationTest.cs" | ||
| // Works with top-level statement AppHosts | ||
| var appHost = await DistributedApplicationTestingBuilder | ||
| .CreateAsync<Projects.MyApp_AppHost>(); |
There was a problem hiding this comment.
Within this article the AppHost type placeholder switches between Projects.MyAppHost and Projects.MyApp_AppHost. Using a single consistent placeholder name throughout will reduce confusion about the generated Projects.* type name format.
| .CreateAsync<Projects.MyApp_AppHost>(); | |
| .CreateAsync<Projects.MyAppHost>(); |
There was a problem hiding this comment.
Fixed Projects.MyApp_AppHost → Projects.MyAppHost throughout the file in c90d20c.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
IEvangelist
left a comment
There was a problem hiding this comment.
This PR looks good so far, but let's double check it for accuracy by using the .github/skills/doc-tester/SKILL.md skill. Please also consider all new comments and feedback/suggestions. @copilot
| ### Override configuration with AppHost arguments | ||
|
|
||
| You can also pass arguments to the AppHost to override configuration values. Arguments are passed to the .NET configuration system, so they can override values from `appsettings.json`: | ||
|
|
||
| ```csharp title="C# — IntegrationTest.cs" | ||
| [Fact] | ||
| public async Task TestWithCustomConfiguration() | ||
| { | ||
| var appHost = await DistributedApplicationTestingBuilder | ||
| .CreateAsync<Projects.MyAppHost>( | ||
| [ | ||
| "--environment=Testing", | ||
| "MyApp:FeatureFlags:NewCheckout=true" | ||
| ]); | ||
|
|
||
| await using var app = await appHost.BuildAsync(); | ||
| await app.StartAsync(); | ||
| } | ||
| ``` | ||
|
|
||
| ### Override AppHost configuration using `DistributedApplicationFactory` | ||
|
|
||
| For more control over the AppHost configuration before any resources are created, use the `DistributedApplicationFactory` class and override the `OnBuilderCreating` lifecycle method: | ||
|
|
||
| ```csharp title="C# — CustomAppHostFactory.cs" | ||
| public class CustomAppHostFactory() | ||
| : DistributedApplicationFactory(typeof(Projects.MyAppHost)) | ||
| { | ||
| protected override void OnBuilderCreating( | ||
| DistributedApplicationOptions applicationOptions, | ||
| HostApplicationBuilderSettings hostOptions) | ||
| { | ||
| hostOptions.Configuration ??= new(); | ||
|
|
||
| // Override configuration before the AppHost is created | ||
| hostOptions.Configuration["MyApp:ConnectionString"] = "Server=test-server;..."; | ||
| hostOptions.Configuration["MyApp:FeatureFlags:NewCheckout"] = "true"; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Use the factory in your test: | ||
|
|
||
| ```csharp title="C# — IntegrationTest.cs" | ||
| [Fact] | ||
| public async Task TestWithCustomFactory() | ||
| { | ||
| await using var factory = new CustomAppHostFactory(); | ||
| await using var app = await factory.BuildAsync(); | ||
| await app.StartAsync(); | ||
|
|
||
| using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); | ||
| await app.ResourceNotifications.WaitForResourceHealthyAsync("api", cts.Token); | ||
|
|
||
| using var httpClient = app.CreateHttpClient("api"); | ||
| using var response = await httpClient.GetAsync("/"); | ||
| Assert.Equal(HttpStatusCode.OK, response.StatusCode); | ||
| } | ||
| ``` | ||
|
|
||
| <LearnMore> | ||
| For more information about the `DistributedApplicationFactory` and its lifecycle methods, see [Manage the AppHost in tests](/testing/manage-app-host/). | ||
| </LearnMore> |
There was a problem hiding this comment.
These two sections seem to already exist, see /testing/manage-app-host/. Perhaps they should be merged with the existing content.
There was a problem hiding this comment.
Removed the duplicate "Override configuration with AppHost arguments" and "Override AppHost configuration using DistributedApplicationFactory" sections in c90d20c. They're now replaced with short prose cross-references and a LearnMore pointing to /testing/manage-app-host/.
| ### Remove wait annotations in tests | ||
|
|
||
| Resources in Aspire can have wait dependencies (via `WaitFor` or `WaitForCompletion`). In some tests, you may want to remove these wait annotations to speed up test startup or to test behavior when dependent resources are unavailable. | ||
|
|
||
| You can remove `WaitAnnotation` instances after building the testing builder, before calling `BuildAsync`: | ||
|
|
||
| ```csharp title="C# — IntegrationTest.cs" | ||
| [Fact] | ||
| public async Task ApiStartsWithoutWaitingForDatabase() | ||
| { | ||
| var appHost = await DistributedApplicationTestingBuilder | ||
| .CreateAsync<Projects.MyAppHost>(); | ||
|
|
||
| // Remove wait annotations from all resources so they start immediately | ||
| foreach (var resource in appHost.Resources) | ||
| { | ||
| var waitAnnotations = resource.Annotations.OfType<WaitAnnotation>().ToList(); | ||
| foreach (var annotation in waitAnnotations) | ||
| { | ||
| resource.Annotations.Remove(annotation); | ||
| } | ||
| } | ||
|
|
||
| await using var app = await appHost.BuildAsync(); | ||
| await app.StartAsync(); | ||
|
|
||
| // Resources start without waiting for dependencies | ||
| } | ||
| ``` | ||
|
|
||
| <Aside type="note"> | ||
| Removing wait annotations means resources start immediately, even if their dependencies aren't ready. Use this technique carefully—only when you intentionally want to test behavior in the absence of proper startup ordering. | ||
| </Aside> |
There was a problem hiding this comment.
Did you actually write temp code and run it, to verify that this works? Please create a temp directory (and delete it after you're done testing that this in fact works). Install the Aspire CLI and test it out.
There was a problem hiding this comment.
Yes — I created a temporary project against Aspire.Hosting.Testing 13.1.1 and verified the code compiles. Both WaitAnnotation (in Aspire.Hosting.ApplicationModel) and the resource.Annotations.Remove(annotation) call build successfully. I also added the missing using Aspire.Hosting.ApplicationModel; directive to the code snippet in c90d20c.
…amples, add AZURE_CLIENT_SECRET Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
Ran doc-tester on both updated pages — all checks pass. The one gap it found (missing Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
| ## Single-file and minimal AppHost considerations | ||
|
|
||
| The `DistributedApplicationTestingBuilder.CreateAsync<T>()` method uses a project reference (from the `Projects` namespace) to locate and start your AppHost. This means your AppHost must be a **project** referenced in your test project. | ||
|
|
||
| <Aside type="note"> | ||
| File-based or single-file AppHosts that aren't structured as .NET projects can't be directly targeted by `DistributedApplicationTestingBuilder.CreateAsync<T>()`. The generic type parameter `T` must be a type from the auto-generated `Projects` namespace, which is only available for project references. | ||
| </Aside> | ||
|
|
||
| If your AppHost uses top-level statements (a common pattern in minimal or single-file AppHosts), it works fine as long as it's a project. Top-level statement projects still produce the necessary project reference metadata: | ||
|
|
||
| ```csharp title="C# — AppHost.cs (top-level statements)" | ||
| // Top-level statements work fine—this is still a project | ||
| var builder = DistributedApplication.CreateBuilder(args); | ||
|
|
||
| builder.AddProject<Projects.Api>("api"); | ||
|
|
||
| builder.Build().Run(); | ||
| ``` | ||
|
|
||
| This AppHost works with the testing builder the same as a class-based AppHost: | ||
|
|
||
| ```csharp title="C# — IntegrationTest.cs" | ||
| // Works with top-level statement AppHosts | ||
| var appHost = await DistributedApplicationTestingBuilder | ||
| .CreateAsync<Projects.MyAppHost>(); | ||
| ``` | ||
|
|
||
| The `Projects` namespace entries are generated based on the `<ProjectReference>` elements in your test project file. Ensure your AppHost project is referenced: | ||
|
|
||
| ```xml title="XML — MyApp.Tests.csproj" | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\MyApp.AppHost\MyApp.AppHost.csproj" /> | ||
| </ItemGroup> | ||
| ``` |
There was a problem hiding this comment.
This entire section needs to be updated to say that C# file-based apps (including apphost.cs) cannot be referenced by test projects. Therefore, you cannot test file-based apps in aspire.
There was a problem hiding this comment.
@copilot - please have a look at this previous comment
There was a problem hiding this comment.
Rewrote the section in bc796d3. It now leads with a <Aside type="caution"> clearly stating that file-based AppHosts (apphost.cs without a .csproj) cannot be tested with DistributedApplicationTestingBuilder, explains why (no project reference → not in Projects namespace), and includes migration steps to convert a file-based AppHost to a standard .csproj project to enable testing.
Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com>
Removed migration steps for file-based AppHost to project structure.
testing/advanced-scenarios.mdx- covers advanced DistributedApplicationTestingBuilder scenarios:WithExplicitStart, conditional resources)WithEnvironment(verified compiles)manage-app-host.mdxfor argument passing and DistributedApplicationFactory (removed duplicate content)apphost.cs(no.csproj) cannot be tested withDistributedApplicationTestingBuilder— no project reference means noProjectsnamespace entry — with migration steps to convert to a project-based AppHost to enable testingtesting/testing-in-ci.mdx- covers CI/CD testing patterns:docs.topics.ts) with full locale translations for both new pagessyncKey="ci-provider"to<Tabs>intesting-in-ci.mdx/whats-new/aspire-13/#defaultazurecredential-behavior-in-azure-deployments(PR Document AZURE_TOKEN_CREDENTIALS breaking change in Aspire 13.0 #424 content)dotnet-versionto'10.x'in GitHub Actions exampleapp.Resources→appHost.Resources(check on builder beforeBuildAsync)AZURE_CLIENT_SECRETforwarding toCiAppHostFactoryexampleProjects.MyApp_AppHost→Projects.MyAppHostfor consistencymanage-app-host.mdx) — replaced withLearnMorecross-referenceusing Aspire.Hosting.ApplicationModel;toWaitAnnotationcode snippetWaitAnnotationremoval code compiles againstAspire.Hosting.Testing13.1.1Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.