The tests of the last lesson call methods. Whether those methods are mapped at the right address, whether the request’s JSON becomes the right record, whether validation kicks in, and whether ProblemDetails with the right status code arrive in the end, they don’t see. All of that happens between the network and the handler, in the ASP.NET Core pipeline. In this lesson you start the whole Kladde in a test and send it real requests. And you learn about a trap that many teams have stepped into: a client that takes a sign-in page for a success.
The Kladde in a test
The Microsoft.AspNetCore.Mvc.Testing package brings the class WebApplicationFactory<TEntryPoint>. It starts an application in the
test process, with exactly its Program.cs, and gives you an HttpClient that talks to it. In doing so,
Program.csreally runs: every registration, every middleware, every endpoint, every page;- it sets the content root to the folder of
Kladde.Web, so thatappsettings.jsonandwwwrootare found; - it replaces Kestrel with the TestServer, a server in memory: the requests don’t go over the network but directly into the pipeline. That makes the tests fast and needs no free port;
- the environment is
Developmentunless the test says otherwise.
TEntryPoint is a class from the application’s assembly, usually Program. Since .NET 10, a source generator creates
public partial class Program for it; before, you had to write that line in Program.cs yourself, and today an analyzer advises you to
delete it.
A first test fetches the start page:
// tests/Kladde.Web.Tests/StartPageTests.cs
using System.Net;
using Microsoft.AspNetCore.Mvc.Testing;
namespace Kladde.Web.Tests;
public class StartPageTests
{
[Fact]
public async Task TheStartPageIsHtmlWithItsHeading()
{
await using WebApplicationFactory<Program> factory = new();
using HttpClient client = factory.CreateClient();
using HttpResponseMessage response = await client.GetAsync("/", TestContext.Current.CancellationToken);
string html = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/html", response.Content.Headers.ContentType?.MediaType);
Assert.Contains("<h1", html);
}
}
The client already knows the test server’s address; that’s why paths like "/" are enough. The factory is torn down with await using
at the end of the test, together with the application.
Switching the test project over
tests/Kladde.Web.Tests needs the same two changes as Kladde.EndToEnd in lesson 2.7.1: the Web SDK in the first line of the
project file, with Content Update instead of Content Include for the xunit.runner.json, and the Microsoft.AspNetCore.Mvc.Testing
package in version 10.0.x. Microsoft names both as prerequisites for integration tests. The handler tests from lesson 2.7.2 keep running
unchanged afterward.