Skip to content

Commit 12f1da5

Browse files
CopilotIEvangelistCopilot
authored
Add testing docs: advanced scenarios and CI/CD patterns (#456)
* Initial plan * Add advanced testing scenarios and CI/CD testing documentation Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> * Address PR review feedback on testing-in-ci and advanced-scenarios docs Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address PR review feedback: remove duplicate content, fix API code examples, add AZURE_CLIENT_SECRET Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> * Rewrite file-based AppHost section to clarify testing limitation Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> * Remove migration instructions for AppHost testing Removed migration steps for file-based AppHost to project structure. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> Co-authored-by: David Pine <david.pine@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent cfb3f6d commit 12f1da5

3 files changed

Lines changed: 557 additions & 0 deletions

File tree

src/frontend/config/sidebar/docs.topics.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,52 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = {
11031103
},
11041104
slug: 'testing/accessing-resources',
11051105
},
1106+
{
1107+
label: 'Advanced testing scenarios',
1108+
translations: {
1109+
da: 'Avancerede testscenarier',
1110+
de: 'Erweiterte Testszenarien',
1111+
en: 'Advanced testing scenarios',
1112+
es: 'Escenarios de pruebas avanzadas',
1113+
fr: 'Scénarios de test avancés',
1114+
hi: 'उन्नत परीक्षण परिदृश्य',
1115+
id: 'Skenario pengujian lanjutan',
1116+
it: 'Scenario di test avanzati',
1117+
ja: '高度なテストシナリオ',
1118+
ko: '고급 테스트 시나리오',
1119+
pt: 'Cenários de teste avançados',
1120+
'pt-BR': 'Cenários de teste avançados',
1121+
'pt-PT': 'Cenários de teste avançados',
1122+
ru: 'Расширенные сценарии тестирования',
1123+
tr: 'Gelişmiş test senaryoları',
1124+
uk: 'Розширені сценарії тестування',
1125+
'zh-CN': '高级测试场景',
1126+
},
1127+
slug: 'testing/advanced-scenarios',
1128+
},
1129+
{
1130+
label: 'Testing in CI/CD pipelines',
1131+
translations: {
1132+
da: 'Test i CI/CD-pipelines',
1133+
de: 'Tests in CI/CD-Pipelines',
1134+
en: 'Testing in CI/CD pipelines',
1135+
es: 'Pruebas en canalizaciones CI/CD',
1136+
fr: 'Tests dans les pipelines CI/CD',
1137+
hi: 'CI/CD पाइपलाइनों में परीक्षण',
1138+
id: 'Pengujian di pipeline CI/CD',
1139+
it: 'Test nelle pipeline CI/CD',
1140+
ja: 'CI/CD パイプラインでのテスト',
1141+
ko: 'CI/CD 파이프라인에서의 테스트',
1142+
pt: 'Testes em pipelines de CI/CD',
1143+
'pt-BR': 'Testes em pipelines de CI/CD',
1144+
'pt-PT': 'Testes em pipelines de CI/CD',
1145+
ru: 'Тестирование в конвейерах CI/CD',
1146+
tr: 'CI/CD ardışık düzenlerinde test',
1147+
uk: 'Тестування в конвеєрах CI/CD',
1148+
'zh-CN': '在 CI/CD 流水线中的测试',
1149+
},
1150+
slug: 'testing/testing-in-ci',
1151+
},
11061152
],
11071153
},
11081154
{
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---
2+
title: Advanced testing scenarios
3+
description: Learn advanced patterns for using DistributedApplicationTestingBuilder, including selectively disabling resources, overriding environment variables, and customizing the test AppHost.
4+
---
5+
6+
import { Aside } from '@astrojs/starlight/components';
7+
import LearnMore from '@components/LearnMore.astro';
8+
9+
This article covers advanced patterns for testing Aspire applications, including selectively disabling resources during tests, overriding environment variables, and customizing the AppHost for specific test scenarios.
10+
11+
## Selectively disable resources in tests
12+
13+
When running integration tests, you might want to exclude certain resources to reduce test complexity or cost—for example, disabling a monitoring dashboard or skipping a sidecar resource that isn't relevant to a particular test.
14+
15+
### Use `WithExplicitStart` to control resource startup
16+
17+
The recommended way to make a resource optional is to use `WithExplicitStart` in the AppHost, and then let tests choose whether to start that resource. Resources marked with `WithExplicitStart` are created but don't start automatically with the rest of the application.
18+
19+
In your AppHost, mark the resource as explicitly started:
20+
21+
```csharp title="C# — AppHost.cs"
22+
var builder = DistributedApplication.CreateBuilder(args);
23+
24+
var api = builder.AddProject<Projects.Api>("api");
25+
26+
// This resource is optional and won't start automatically
27+
builder.AddContainer("monitoring", "grafana/grafana")
28+
.WithExplicitStart();
29+
30+
builder.Build().Run();
31+
```
32+
33+
In your test, you can start the resource manually if needed, or leave it stopped:
34+
35+
```csharp title="C# — IntegrationTest.cs"
36+
[Fact]
37+
public async Task ApiWorksWithoutMonitoring()
38+
{
39+
var appHost = await DistributedApplicationTestingBuilder
40+
.CreateAsync<Projects.MyAppHost>();
41+
42+
await using var app = await appHost.BuildAsync();
43+
await app.StartAsync();
44+
45+
// The "monitoring" resource is not started—only "api" is running
46+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
47+
await app.ResourceNotifications.WaitForResourceHealthyAsync("api", cts.Token);
48+
49+
using var httpClient = app.CreateHttpClient("api");
50+
using var response = await httpClient.GetAsync("/health");
51+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
52+
}
53+
```
54+
55+
### Conditionally add resources based on configuration
56+
57+
Another approach is to use configuration in your AppHost to conditionally add resources. This gives tests control over which resources are included:
58+
59+
```csharp title="C# — AppHost.cs"
60+
var builder = DistributedApplication.CreateBuilder(args);
61+
62+
var api = builder.AddProject<Projects.Api>("api");
63+
64+
// Only add the database resource if not explicitly disabled
65+
if (builder.Configuration.GetValue("AddDatabase", true))
66+
{
67+
var db = builder.AddPostgres("postgres").AddDatabase("mydb");
68+
api.WithReference(db);
69+
}
70+
71+
builder.Build().Run();
72+
```
73+
74+
In tests, pass the configuration argument to skip the database:
75+
76+
```csharp title="C# — IntegrationTest.cs"
77+
[Fact]
78+
public async Task ApiStartsWithoutDatabase()
79+
{
80+
var appHost = await DistributedApplicationTestingBuilder
81+
.CreateAsync<Projects.MyAppHost>(["AddDatabase=false"]);
82+
83+
// Assert that the "postgres" resource doesn't exist
84+
Assert.DoesNotContain(appHost.Resources, r => r.Name == "postgres");
85+
86+
await using var app = await appHost.BuildAsync();
87+
await app.StartAsync();
88+
89+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
90+
await app.ResourceNotifications.WaitForResourceAsync(
91+
"api", KnownResourceStates.Running, cts.Token);
92+
}
93+
```
94+
95+
### Remove wait annotations in tests
96+
97+
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.
98+
99+
You can remove `WaitAnnotation` instances after building the testing builder, before calling `BuildAsync`:
100+
101+
```csharp title="C# — IntegrationTest.cs"
102+
using Aspire.Hosting.ApplicationModel;
103+
104+
[Fact]
105+
public async Task ApiStartsWithoutWaitingForDatabase()
106+
{
107+
var appHost = await DistributedApplicationTestingBuilder
108+
.CreateAsync<Projects.MyAppHost>();
109+
110+
// Remove wait annotations from all resources so they start immediately
111+
foreach (var resource in appHost.Resources)
112+
{
113+
var waitAnnotations = resource.Annotations.OfType<WaitAnnotation>().ToList();
114+
foreach (var annotation in waitAnnotations)
115+
{
116+
resource.Annotations.Remove(annotation);
117+
}
118+
}
119+
120+
await using var app = await appHost.BuildAsync();
121+
await app.StartAsync();
122+
123+
// Resources start without waiting for dependencies
124+
}
125+
```
126+
127+
<Aside type="note">
128+
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.
129+
</Aside>
130+
131+
## Override environment variables in tests
132+
133+
Because Aspire tests run services in separate processes, you can't inject services directly through dependency injection. However, you can influence application behavior through environment variables or configuration.
134+
135+
### Override environment variables via the AppHost builder
136+
137+
Use `WithEnvironment` on the resource builder after creating the testing builder to set environment variables for specific resources. You access the resource builder through the `CreateResourceBuilder` method:
138+
139+
```csharp title="C# — IntegrationTest.cs"
140+
[Fact]
141+
public async Task ApiUsesTestFeatureFlag()
142+
{
143+
var appHost = await DistributedApplicationTestingBuilder
144+
.CreateAsync<Projects.MyAppHost>();
145+
146+
// Override an environment variable for the "api" resource
147+
appHost.CreateResourceBuilder<ProjectResource>("api")
148+
.WithEnvironment("FeatureFlags__NewCheckout", "true");
149+
150+
await using var app = await appHost.BuildAsync();
151+
await app.StartAsync();
152+
153+
// The "api" service now runs with the overridden feature flag
154+
}
155+
```
156+
157+
### Override configuration with AppHost arguments
158+
159+
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`. For more information, see [Pass arguments to your AppHost](/testing/manage-app-host/#pass-arguments-to-your-apphost).
160+
161+
### Override AppHost configuration before resources are created
162+
163+
For more control over the AppHost configuration before any resources are created, use the `DistributedApplicationFactory` class and override the `OnBuilderCreating` lifecycle method. For more information, see [Use the `DistributedApplicationFactory` class](/testing/manage-app-host/#use-the-distributedapplicationfactory-class).
164+
165+
<LearnMore>
166+
For full documentation on argument passing and the `DistributedApplicationFactory` lifecycle, see [Manage the AppHost in tests](/testing/manage-app-host/).
167+
</LearnMore>
168+
169+
## File-based AppHost limitations
170+
171+
The Aspire CLI supports **file-based AppHosts** — a lightweight `apphost.cs` file with no `.csproj` project file. These are created with `aspire init` or the `aspire-apphost-singlefile` template and are run directly with `aspire run`.
172+
173+
<Aside type="caution">
174+
File-based AppHosts (`apphost.cs` without a `.csproj`) **cannot** be tested with `DistributedApplicationTestingBuilder`. Because they don't have a project file, they don't appear in the auto-generated `Projects` namespace, and there's no project reference to pass to `CreateAsync<T>()`.
175+
</Aside>
176+
177+
The `DistributedApplicationTestingBuilder.CreateAsync<T>()` method requires a type from the `Projects` namespace:
178+
179+
```csharp title="C# — IntegrationTest.cs"
180+
// This requires Projects.MyAppHost to exist — only available via ProjectReference
181+
var appHost = await DistributedApplicationTestingBuilder
182+
.CreateAsync<Projects.MyAppHost>();
183+
```
184+
185+
The `Projects` namespace entries are only generated for `<ProjectReference>` entries in your test project. A file-based `apphost.cs` produces no such entry.
186+
187+
## See also
188+
189+
- [Manage the AppHost in tests](/testing/manage-app-host/)
190+
- [Access resources in tests](/testing/accessing-resources/)
191+
- [Testing overview](/testing/overview/)

0 commit comments

Comments
 (0)