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.cs really runs: every registration, every middleware, every endpoint, every page;
  • it sets the content root to the folder of Kladde.Web, so that appsettings.json and wwwroot are 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 Development unless 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:

C#
// 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.