Learn / .NET API Engineering Path
Testing, Health, and Hosted Services
Add integration tests, health checks, structured logs, and a background worker that shuts down gracefully.
Course: .NET API Engineering Path. Level: Advanced. Topic: Typed backend.
Stage: pro - Pro operations - Production operations and reliability. Finish the path by proving the API behaves under real hosting concerns: tests, readiness, logging, background work, and shutdown.
Outcomes
- Test endpoints with WebApplicationFactory.
- Expose liveness and readiness checks.
- Implement a cancellable hosted service.
- Write focused tests with xUnit-style arrange, act, assert structure.
- Use fakes or test doubles at external boundaries.
- Test ASP.NET Core endpoints with an in-memory test host.
Concepts
- WebApplicationFactory
- health check
- ILogger
- IHostedService
- Testing .NET Applications
- Unit tests protect behavior
- Integration tests verify wiring
- Modern C# and .NET foundations
- Guided practice
Concept flow
Show how production operations and reliability moves from trigger to implementation outcome in .NET APIs.
- Test host
- Endpoint
- Service
- DbContext
- Health check
- Hosted worker
Session flow
- Model WebApplicationFactory (concept, 11 min) — Name the decisions behind WebApplicationFactory before writing code.
- Test endpoints with WebApplicationFactory.
- Explain where WebApplicationFactory belongs in order management API.
- Build the vertical slice (walkthrough, 20 min) — Implement the smallest useful slice in Workers/ExpireOrdersWorker.cs.
- Expose liveness and readiness checks.
- Connect health check to a working example.
- Verify and harden (exercise, 14 min) — Make a background loop observe cancellation.
- Implement a cancellable hosted service.
- Record one risk or follow-up before moving on.
- Unit and Integration Testing: Unit tests protect behavior (concept, 48 min) — A good unit test is deterministic, fast, and focused on a behavior. It should not depend on wall-clock time, random data, network services, or test order. Inject boundaries such as clocks and ID generators when needed.
- Retained source example: xUnit test
public sealed class InventoryItemTests
{
[Fact]
public void Reserve_reduces_available_quantity()
{
var item = new InventoryItem("ABC-123", quantity: 5);
item.Reserve(2);
Assert.Equal(3, item.Quantity);
}
}
The test checks one behavior and does not require a database, web server, or filesystem.
- Unit and Integration Testing: Integration tests verify wiring (walkthrough, 48 min) — Integration tests should cover boundaries that unit tests cannot, such as routing, middleware, model binding, database mappings, authentication configuration, and serialization. ASP.NET Core's WebApplicationFactory can host an app in memory for HTTP-level tests.
- Retained source example: Endpoint integration test shape
public sealed class ProductApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public ProductApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Missing_product_returns_not_found()
{
var response = await _client.GetAsync("/products/00000000-0000-0000-0000-000000000001");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
This verifies actual routing and response behavior without starting a real network listener.
- Unit and Integration Testing: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Name tests after the behavior they protect.
- Practice: Keep unit tests fast and independent.
- Practice: Use integration tests for wiring, serialization, middleware, database mappings, and authorization.
- Practice: Run tests in CI with warnings treated seriously.
- Avoid: Mocking the class under test instead of its dependencies.
- Avoid: Testing private implementation details rather than public behavior.
- Avoid: Relying on test order or shared mutable static state.
- Unit and Integration Testing: references (review, 2 min) — Original references retained from the legacy library.
- Unit testing C# with xUnit: https://learn.microsoft.com/dotnet/core/testing/unit-testing-with-dotnet-test
- Integration tests in ASP.NET Core: https://learn.microsoft.com/aspnet/core/test/integration-tests
Code example
C# in Workers/ExpireOrdersWorker.cs.
public sealed class ExpireOrdersWorker(IOrders orders, ILogger<ExpireOrdersWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await orders.ExpireStaleAsync(stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
Walkthrough examples
- Testing, Health, and Hosted Services in a order management API — A team is extending a production-grade ASP.NET Core order API and needs this lesson's pattern to be clear enough for review, testing, and future maintenance.
- File: Workers/ExpireOrdersWorker.cs
- File: src/Orders.Application/testing-health-and-hosted-services.cs
- File: tests/Orders.Api.Tests/testing-health-and-hosted-services.Tests.cs
- File: docs/dotnet-advanced/testing-health-and-hosted-services.md
- Start from the provided C# snippet and make the intent visible in names and boundaries.
- Apply the checklist item "Test serialization through HTTP" before adding extra behavior.
- Write down how the implementation changes when health check fails or becomes slow.
- xUnit test — The test checks one behavior and does not require a database, web server, or filesystem.
- Retained source code:
public sealed class InventoryItemTests
{
[Fact]
public void Reserve_reduces_available_quantity()
{
var item = new InventoryItem("ABC-123", quantity: 5);
item.Reserve(2);
Assert.Equal(3, item.Quantity);
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Endpoint integration test shape — This verifies actual routing and response behavior without starting a real network listener.
- Retained source code:
public sealed class ProductApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public ProductApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Missing_product_returns_not_found()
{
var response = await _client.GetAsync("/products/00000000-0000-0000-0000-000000000001");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Fake clock for deterministic tests — Replacing time with an injected dependency makes tests stable and removes sleeps.
- Retained source code:
public sealed class FixedClock(DateTimeOffset now) : IClock
{
public DateTimeOffset UtcNow { get; } = now;
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
Practice
- Write one endpoint integration test.
- Add a database readiness check.
- Make a background loop observe cancellation.
- Choose the test level: You want to verify that authorization middleware rejects anonymous requests. Is this best as a unit test or integration test?
- Starter code: test_level = ____
- Expected output: integration
- Hint: Middleware order and authentication configuration are application wiring concerns.
- Reference solution: Use an integration test because the behavior depends on middleware and host configuration.
- Accepted answers: integration | integration test
Checklist
- Test serialization through HTTP
- Log structured fields
- Separate liveness from readiness
- Stop hosted services gracefully
Quiz prompts
- What should readiness communicate to an orchestrator? — Readiness is a traffic-routing signal, often dependent on required services such as the database.
- A teammate wants to hide WebApplicationFactory inside a convenient helper. What should you check first? — Place WebApplicationFactory at the boundary that keeps order management API behavior explicit, testable, and reviewable.
- Which artifact best proves this .NET APIs lesson is ready for review? — Production-ready learning needs evidence: a test, trace, command, screenshot, or log that catches the risk again.
- Pro operations: a teammate says the happy path works, but "Integration tests and health signals" is still implicit. What should you ask for before merging? — Integration tests and health signals belongs in the pro stage only when the decision is visible, testable, and tied to a realistic failure mode.
- A reviewer has five minutes to evaluate this pro .NET APIs slice. Which evidence is strongest? — Complete the pro-grade host: integration tests, readiness/liveness split, structured logs, cancellable hosted worker, and a deployment note a teammate can run.
- What should a deterministic unit test avoid? — External timing and network dependencies make tests flaky and slow.
Flashcards
- Pro operations: what decision does "Integration tests and health signals" force you to make? Use the real ASP.NET Core host boundary to verify serialization, dependencies, liveness, and readiness. Evidence prompt: Write one WebApplicationFactory test and document exactly what readiness checks.
- Pro operations: what decision does "Hosted services and graceful shutdown" force you to make? Make background work observable, cancellable, and safe during deploys. Evidence prompt: Add a cancellation test or manual proof for the hosted worker loop.
- Pro operations: what decision does "Deployment runbook and rollback signals" force you to make? Define the health, log, and metric signals that decide whether a deployment continues or rolls back. Evidence prompt: Write a two-minute deploy runbook with the exact readiness, log, and rollback checks a teammate should run.
- In .NET APIs, what should you remember about WebApplicationFactory? WebApplicationFactory matters here because it supports "Test endpoints with WebApplicationFactory.".
- In .NET APIs, what should you remember about health check? health check matters here because it supports "Expose liveness and readiness checks.".
- In .NET APIs, what should you remember about ILogger? ILogger matters here because it supports "Implement a cancellable hosted service.".
- In .NET APIs, what should you remember about IHostedService? IHostedService matters here because it supports "Test endpoints with WebApplicationFactory.".
Labs
- Ship a testing, health, and hosted services slice — Extend a production-grade ASP.NET Core order API with a small but reviewable feature that proves the lesson's architecture in code.
- Complete the pro-grade host: integration tests, readiness/liveness split, structured logs, cancellable hosted worker, and a deployment note a teammate can run.
- Write one WebApplicationFactory test and document exactly what readiness checks.
- Add a cancellation test or manual proof for the hosted worker loop.
- Write a two-minute deploy runbook with the exact readiness, log, and rollback checks a teammate should run.
- Write one endpoint integration test.
- Add a database readiness check.
- The lab demonstrates the pro operations outcome without skipping earlier contract evidence.
- Each subtopic has a concrete artifact: Integration tests and health signals, Hosted services and graceful shutdown, Deployment runbook and rollback signals.
- The review notes explain how this level changes ownership, verification, or operational risk.
Challenge
- Review-ready testing, health, and hosted services (Stretch) — Complete the pro-grade host: integration tests, readiness/liveness split, structured logs, cancellable hosted worker, and a deployment note a teammate can run.
- All checklist items are either implemented or documented with a reason.
- The change can be understood from Workers/ExpireOrdersWorker.cs plus one short note.
- The concept diagram names ownership, failure handling, and verification points.
- A teammate could run the verification steps without asking for hidden context.
Canonical lesson URL