Skip to content

Commit 2f62f1c

Browse files
authoredFeb 26, 2025
Merge pull request #74 from dotnet-presentations/jongalloway/add-integration-testing-1
Add integration testing module using Aspire.Hosting.Testing and MSTest
2 parents f9ef21f + a00766c commit 2f62f1c

7 files changed

+377
-1
lines changed
 

‎README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ You can also watch the Let's Learn .NET Aspire live stream events for the follow
4444

4545
## Workshop
4646

47-
This .NET Aspire workshop is part of the [Let's Learn .NET](https://aka.ms/letslearndotnet) series. This workshop is designed to help you learn about .NET Aspire and how to use it to build cloud ready applications. This workshop is broken down into 8 modules:
47+
This .NET Aspire workshop is part of the [Let's Learn .NET](https://aka.ms/letslearndotnet) series. This workshop is designed to help you learn about .NET Aspire and how to use it to build cloud ready applications. This workshop is broken down into 9 modules:
4848

4949
1. [Setup & Installation](./workshop/1-setup.md)
5050
1. [Service Defaults](./workshop/2-servicedefaults.md)
@@ -54,6 +54,7 @@ This .NET Aspire workshop is part of the [Let's Learn .NET](https://aka.ms/letsl
5454
1. [Deployment](./workshop/6-deployment.md)
5555
1. [Telemetry Module](./workshop/7-telemetry.md)
5656
1. [Database Module](./workshop/8-database.md)
57+
1. [Integration Testing](./workshop/9-integration-testing.md)
5758

5859
A full slide deck is available for this workshop [here](./workshop/AspireWorkshop.pptx).
5960

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Aspire.Hosting;
2+
3+
namespace MyWeatherHub.Tests;
4+
5+
[TestClass]
6+
public class EnvVarTests
7+
{
8+
[TestMethod]
9+
public async Task WebResourceEnvVarsResolveToApiService()
10+
{
11+
// Arrange
12+
var appHost = await DistributedApplicationTestingBuilder
13+
.CreateAsync<Projects.AppHost>();
14+
15+
var frontend = (IResourceWithEnvironment)appHost.Resources
16+
.Single(static r => r.Name == "myweatherhub");
17+
18+
// Act
19+
var envVars = await frontend.GetEnvironmentVariableValuesAsync(
20+
DistributedApplicationOperation.Publish);
21+
22+
// Assert
23+
CollectionAssert.Contains(envVars,
24+
new KeyValuePair<string, string>(
25+
key: "services__api__https__0",
26+
value: "{api.bindings.https.url}"));
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Net.Http.Json;
2+
3+
namespace MyWeatherHub.Tests;
4+
5+
[TestClass]
6+
public class IntegrationTests
7+
{
8+
[TestMethod]
9+
public async Task TestApiGetZones()
10+
{
11+
// Arrange
12+
var appHost = await DistributedApplicationTestingBuilder
13+
.CreateAsync<Projects.AppHost>();
14+
15+
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
16+
{
17+
clientBuilder.AddStandardResilienceHandler();
18+
});
19+
20+
await using var app = await appHost.BuildAsync();
21+
22+
var resourceNotificationService = app.Services
23+
.GetRequiredService<ResourceNotificationService>();
24+
25+
await app.StartAsync();
26+
27+
// Act
28+
var httpClient = app.CreateHttpClient("api");
29+
30+
await resourceNotificationService.WaitForResourceAsync(
31+
"api",
32+
KnownResourceStates.Running
33+
)
34+
.WaitAsync(TimeSpan.FromSeconds(30));
35+
36+
var response = await httpClient.GetAsync("/zones");
37+
38+
// Assert
39+
response.EnsureSuccessStatusCode();
40+
var zones = await response.Content.ReadFromJsonAsync<Zone[]>();
41+
Assert.IsNotNull(zones);
42+
Assert.IsTrue(zones.Length > 0);
43+
}
44+
45+
[TestMethod]
46+
public async Task TestWebAppHomePage()
47+
{
48+
// Arrange
49+
var appHost = await DistributedApplicationTestingBuilder
50+
.CreateAsync<Projects.AppHost>();
51+
52+
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
53+
{
54+
clientBuilder.AddStandardResilienceHandler();
55+
});
56+
57+
await using var app = await appHost.BuildAsync();
58+
59+
var resourceNotificationService = app.Services
60+
.GetRequiredService<ResourceNotificationService>();
61+
62+
await app.StartAsync();
63+
64+
// Act
65+
var httpClient = app.CreateHttpClient("myweatherhub");
66+
67+
await resourceNotificationService.WaitForResourceAsync(
68+
"myweatherhub",
69+
KnownResourceStates.Running
70+
)
71+
.WaitAsync(TimeSpan.FromSeconds(30));
72+
73+
var response = await httpClient.GetAsync("/");
74+
75+
// Assert
76+
response.EnsureSuccessStatusCode();
77+
var content = await response.Content.ReadAsStringAsync();
78+
Assert.IsTrue(content.Contains("MyWeatherHub"));
79+
}
80+
}
81+
82+
public record Zone(string Key, string Name, string State);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
<IsTestProject>true</IsTestProject>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<EnableMSTestRunner>true</EnableMSTestRunner>
13+
<OutputType>Exe</OutputType>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Aspire.Hosting.Testing" Version="9.1.0" />
18+
<PackageReference Include="MSTest" Version="3.8.2" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\AppHost\AppHost.csproj" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<Using Include="System.Net" />
27+
<Using Include="Microsoft.Extensions.DependencyInjection" />
28+
<Using Include="Aspire.Hosting.ApplicationModel" />
29+
<Using Include="Aspire.Hosting.Testing" />
30+
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
31+
</ItemGroup>
32+
33+
</Project>

‎complete/MyWeatherHub.sln

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDefaults", "ServiceD
1111
EndProject
1212
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api", "Api\Api.csproj", "{BC43CA33-61F7-478B-91BD-9F4B7B8BA23B}"
1313
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}"
15+
EndProject
1416
Global
1517
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1618
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
3335
{BC43CA33-61F7-478B-91BD-9F4B7B8BA23B}.Debug|Any CPU.Build.0 = Debug|Any CPU
3436
{BC43CA33-61F7-478B-91BD-9F4B7B8BA23B}.Release|Any CPU.ActiveCfg = Release|Any CPU
3537
{BC43CA33-61F7-478B-91BD-9F4B7B8BA23B}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Release|Any CPU.Build.0 = Release|Any CPU
3642
EndGlobalSection
3743
GlobalSection(SolutionProperties) = preSolution
3844
HideSolutionNode = FALSE

‎workshop/8-database.md

+6
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,9 @@ Data API Builder (DAB) automatically generates REST and GraphQL endpoints from y
264264
- Custom policy support
265265
- Real-time updates via GraphQL subscriptions
266266
- Database schema-driven API design
267+
268+
## Conclusion
269+
270+
In this module, we added PostgreSQL database support to our application using .NET Aspire's database integration features. We used Entity Framework Core for data access and configured our application to work with both local development and cloud-hosted databases.
271+
272+
The natural next step would be to add tests to verify the database integration works correctly. Head over to [Module #9: Integration Testing](./9-integration-testing.md) to learn how to write integration tests for your .NET Aspire application.

0 commit comments

Comments
 (0)