Integrationstests in ASP.NET CoreIntegration tests in ASP.NET Core

Von Javier Calvarro Nelson, Steve Smith und Jos van der TilBy Javier Calvarro Nelson, Steve Smith, and Jos van der Til

Integrationstests stellen sicher, dass die Komponenten einer App auf einer Ebene, die die unterstützende Infrastruktur der App (wie die Datenbank, das Dateisystem und das Netzwerk) umfasst, ordnungsgemäß funktionieren.Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, such as the database, file system, and network. ASP.NET Core unterstützt Integrationstests mithilfe eines Komponententest-Frameworks mit einem Testwebhost und einem In-Memory-Testserver.ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server.

In diesem Thema werden Grundkenntnisse über Komponententests vorausgesetzt.This topic assumes a basic understanding of unit tests. Wenn Sie nicht mit Testkonzepten vertraut sind, lesen Sie das Thema Komponententests in .NET Core und .NET Standard und zugehörige Inhalte.If unfamiliar with test concepts, see the Unit Testing in .NET Core and .NET Standard topic and its linked content.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

Bei der Beispiel-App handelt es sich um eine Razor Pages-App, hierfür werden grundlegende Kenntnisse über Razor Pages vorausgesetzt.The sample app is a Razor Pages app and assumes a basic understanding of Razor Pages. Wenn Sie nicht mit Razor Pages vertraut sind, lesen Sie die folgenden Themen:If unfamiliar with Razor Pages, see the following topics:

Hinweis

Zum Testen von Single-Page-Webanwendungen empfiehlt es sich, ein Tool wie Selenium zu verwenden, mit dem ein Browser automatisiert werden kann.For testing SPAs, we recommended a tool such as Selenium, which can automate a browser.

Einführung in IntegrationstestsIntroduction to integration tests

Integrationstests bewerten die Komponenten einer App auf breiterer Ebene als Komponententests.Integration tests evaluate an app's components on a broader level than unit tests. Komponententests werden verwendet, um isolierte Softwarekomponenten wie z. B. einzelne Klassenmethoden zu testen.Unit tests are used to test isolated software components, such as individual class methods. Integrationstests bestätigen, dass zwei oder mehr App-Komponenten zusammenarbeiten, um ein erwartetes Ergebnis zu erzielen, ggf. auch unter Einbindung aller Komponenten, die für die vollständige Verarbeitung einer Anforderung erforderlich sind.Integration tests confirm that two or more app components work together to produce an expected result, possibly including every component required to fully process a request.

Diese umfassenderen Tests werden verwendet, um die Infrastruktur und das gesamte Framework der App zu testen, häufig einschließlich der folgenden Komponenten:These broader tests are used to test the app's infrastructure and whole framework, often including the following components:

  • DatenbankDatabase
  • DateisystemFile system
  • Netzwerk-AppliancesNetwork appliances
  • Anforderung/Antwort-PipelineRequest-response pipeline

Komponententests verwenden anstelle von Infrastrukturkomponenten künstliche Komponenten, die als Fakes oder Pseudoobjekte bezeichnet werden.Unit tests use fabricated components, known as fakes or mock objects, in place of infrastructure components.

Im Gegensatz zu Komponententests gilt für Integrationstests:In contrast to unit tests, integration tests:

  • Sie verwenden die tatsächlichen Komponenten, die von der App in der Produktionsumgebung verwendet werden.Use the actual components that the app uses in production.
  • Sie erfordern mehr Code und Datenverarbeitung.Require more code and data processing.
  • Ihre Ausführung dauert länger.Take longer to run.

Beschränken Sie daher die Verwendung von Integrationstests auf die wichtigsten Infrastrukturszenarios.Therefore, limit the use of integration tests to the most important infrastructure scenarios. Wenn ein Verhalten mithilfe eines Komponententests oder eines Integrationstests getestet werden kann, wählen Sie den Komponententest.If a behavior can be tested using either a unit test or an integration test, choose the unit test.

Tipp

Schreiben Sie keine Integrationstests für jede denkbare Permutation des Daten- und Dateizugriffs bei Datenbanken und Dateisystemen.Don't write integration tests for every possible permutation of data and file access with databases and file systems. Unabhängig davon, wie viele Elemente in einer App mit Datenbanken und Dateisystemen interagieren, ist ein fokussierter Satz von Lese-, Schreib-, Update- und Lösch-Integrationstests üblicherweise in der Lage, die Datenbank- und Dateisystemkomponenten adäquat zu testen.Regardless of how many places across an app interact with databases and file systems, a focused set of read, write, update, and delete integration tests are usually capable of adequately testing database and file system components. Verwenden Sie Komponententests für Routinetests der Methodenlogik, die mit diesen Komponenten interagieren.Use unit tests for routine tests of method logic that interact with these components. Bei Komponententests führt die Verwendung von Infrastruktur-Fakes/-Pseudoobjekten zu einer schnelleren Testausführung.In unit tests, the use of infrastructure fakes/mocks result in faster test execution.

Hinweis

Bei der Besprechung von Integrationstests wird das getestete Projekt häufig als getestetes System oder kurz „GS“ bezeichnet.In discussions of integration tests, the tested project is frequently called the System Under Test, or "SUT" for short.

In diesem Thema wird „GS“ verwendet, um auf die getestete ASP.NET Core-App zu verweisen."SUT" is used throughout this topic to refer to the tested ASP.NET Core app.

Integrationstests in ASP.NET CoreASP.NET Core integration tests

Integrationstests in ASP.NET Core erfordern Folgendes:Integration tests in ASP.NET Core require the following:

  • Es wird ein Testprojekt verwendet, um die Tests einzugrenzen und auszuführen.A test project is used to contain and execute the tests. Das Testprojekt enthält einen Verweis auf das GS.The test project has a reference to the SUT.
  • Das Testprojekt erstellt einen Testwebhost für das GS und verwendet einen Testserverclient, um Anforderungen und Antworten im Zusammenhang mit dem GS zu verarbeiten.The test project creates a test web host for the SUT and uses a test server client to handle requests and responses with the SUT.
  • Um die Tests auszuführen und die Testergebnisse zu melden, wird ein Test-Runner verwendet.A test runner is used to execute the tests and report the test results.

Integrationstests folgen einer Sequenz von Ereignissen, die die üblichen Testschritte Arrange, Act und Assert umfassen:Integration tests follow a sequence of events that include the usual Arrange, Act, and Assert test steps:

  1. Der Webhost des GS wird konfiguriert.The SUT's web host is configured.
  2. Es wird ein Testserverclient erstellt, um Anforderungen an die App zu senden.A test server client is created to submit requests to the app.
  3. Der Testschritt Arrange wird ausgeführt: Die Test-App bereitet eine Anforderung vor.The Arrange test step is executed: The test app prepares a request.
  4. Der Testschritt Act wird ausgeführt: Der Client sendet die Anforderung und empfängt die Antwort.The Act test step is executed: The client submits the request and receives the response.
  5. Der Testschritt Assert wird ausgeführt: Die tatsächliche Antwort wird je nach der erwarteten Antwort als Pass oder Fail bewertet.The Assert test step is executed: The actual response is validated as a pass or fail based on an expected response.
  6. Der Prozess wird so lange fortgesetzt, bis alle Tests ausgeführt wurden.The process continues until all of the tests are executed.
  7. Die Testergebnisse werden gemeldet.The test results are reported.

Üblicherweise ist der Testwebhost anders konfiguriert als der normale Webhost der App für die Testläufe.Usually, the test web host is configured differently than the app's normal web host for the test runs. Beispielsweise könnten für die Tests eine andere Datenbank oder andere App-Einstellungen verwendet werden.For example, a different database or different app settings might be used for the tests.

Infrastrukturkomponenten wie der Testwebhost und der In-Memory-Testserver (TestServer) werden durch das Microsoft.AspNetCore.Mvc.Testing-Paket bereitgestellt oder verwaltet.Infrastructure components, such as the test web host and in-memory test server (TestServer), are provided or managed by the Microsoft.AspNetCore.Mvc.Testing package. Durch die Verwendung dieses Pakets werden die Testerstellung und -ausführung optimiert.Use of this package streamlines test creation and execution.

Das Microsoft.AspNetCore.Mvc.Testing-Paket verarbeitet die folgenden Aufgaben:The Microsoft.AspNetCore.Mvc.Testing package handles the following tasks:

  • Es kopiert die Datei für Abhängigkeiten ( .deps) aus dem GS in das bin- Verzeichnis des Testprojekts.Copies the dependencies file (.deps) from the SUT into the test project's bin directory.
  • Es legt das Inhaltsstammelement auf das Projektstammelement des GS fest, damit statische Dateien und Seiten/Ansichten bei der Ausführung der Tests gefunden werden.Sets the content root to the SUT's project root so that static files and pages/views are found when the tests are executed.
  • Es stellt die Klasse WebApplicationFactory zur Optimierung des Bootstrappings des GS mit TestServer bereit.Provides the WebApplicationFactory class to streamline bootstrapping the SUT with TestServer.

In der Dokumentation der Komponententests wird beschrieben, wie Sie ein Testprojekt und einen Test-Runner einrichten. Ferner finden Sie dort ausführliche Anweisungen zum Ausführen von Tests sowie Empfehlungen zum Benennen von Tests und Testklassen.The unit tests documentation describes how to set up a test project and test runner, along with detailed instructions on how to run tests and recommendations for how to name tests and test classes.

Hinweis

Wenn Sie ein Testprojekt für eine App erstellen, verwenden Sie unterschiedliche Projekte für die Komponententests und die Integrationstests.When creating a test project for an app, separate the unit tests from the integration tests into different projects. Dadurch wird sichergestellt, dass Komponenten für Infrastrukturtests nicht versehentlich in die Komponententests eingeschlossen werden.This helps ensure that infrastructure testing components aren't accidentally included in the unit tests. Durch die Trennung von Komponenten- und Integrationstests können Sie außerdem steuern, welche Testsätze ausgeführt werden.Separation of unit and integration tests also allows control over which set of tests are run.

Es gibt praktisch keinen Unterschied zwischen der Konfiguration für Tests von Razor Pages-Apps und MVC-Apps.There's virtually no difference between the configuration for tests of Razor Pages apps and MVC apps. Der einzige Unterschied besteht darin, wie die Tests benannt werden.The only difference is in how the tests are named. In einer Razor Pages-App werden Tests von Seitenendpunkten normalerweise nach der Seitenmodellklasse benannt (z. B. IndexPageTests für das Testen der Komponentenintegration für die Indexseite).In a Razor Pages app, tests of page endpoints are usually named after the page model class (for example, IndexPageTests to test component integration for the Index page). In einer MVC-App werden Tests in der Regel nach Controllerklassen organisiert und nach den von ihnen getesteten Controllern benannt (z. B. HomeControllerTests für das Testen der Komponentenintegration für den Startseitencontroller).In an MVC app, tests are usually organized by controller classes and named after the controllers they test (for example, HomeControllerTests to test component integration for the Home controller).

Voraussetzungen für Test-AppsTest app prerequisites

Für das Testprojekt muss Folgendes erfüllt sein:The test project must:

Diese Voraussetzungen können Sie in der Beispiel-App sehen.These prerequisites can be seen in the sample app. Untersuchen Sie die Datei tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj.Inspect the tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj file. Die Beispiel-App verwendet das xUnit-Testframework und die AngleSharp-Parserbibliothek, sodass die Beispiel-APP auch auf Folgendes verweist:The sample app uses the xUnit test framework and the AngleSharp parser library, so the sample app also references:

Entity Framework Core wird ebenfalls in den Tests verwendet.Entity Framework Core is also used in the tests. Die App verweist auf:The app references:

GS-UmgebungSUT environment

Wenn die Umgebung des GS nicht festgelegt ist, wird standardmäßig die Entwicklungsumgebung verwendet.If the SUT's environment isn't set, the environment defaults to Development.

Grundlegende Tests mit der Standard-WebApplicationFactoryBasic tests with the default WebApplicationFactory

WebApplicationFactory<TEntryPoint> wird verwendet, um einen TestServer für die Integrationstests zu erstellen.WebApplicationFactory<TEntryPoint> is used to create a TestServer for the integration tests. TEntryPoint ist die Einstiegspunktklasse des GS, in der Regel die Startup-Klasse.TEntryPoint is the entry point class of the SUT, usually the Startup class.

Testklassen implementieren eine class fixture-Schnittstelle (IClassFixture), um anzugeben, dass die Klasse Tests enthält und um in den Tests in der Klasse gemeinsame Objektinstanzen bereitzustellen.Test classes implement a class fixture interface (IClassFixture) to indicate the class contains tests and provide shared object instances across the tests in the class.

Die folgende Testklasse (BasicTests) verwendet die WebApplicationFactory für den Bootstrap des GS und um einen HttpClient für eine Testmethode Get_EndpointsReturnSuccessAndCorrectContentType bereitzustellen.The following test class, BasicTests, uses the WebApplicationFactory to bootstrap the SUT and provide an HttpClient to a test method, Get_EndpointsReturnSuccessAndCorrectContentType. Die Methode prüft, ob der Antwortstatuscode erfolgreich ist (Statuscodes im Bereich 200-299) und der Content-Type-Header für mehrere App-Seiten text/html; charset=utf-8 lautet.The method checks if the response status code is successful (status codes in the range 200-299) and the Content-Type header is text/html; charset=utf-8 for several app pages.

CreateClient erstellt eine Instanz von HttpClient, die automatisch Umleitungen folgt und cookies verarbeitet.CreateClient creates an instance of HttpClient that automatically follows redirects and handles cookies.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;

    public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Standardmäßig werden nicht erforderliche cookies nicht über Anforderungen hinweg beibehalten, wenn die DSGVO-Zustimmungsrichtlinie aktiviert ist.By default, non-essential cookies aren't preserved across requests when the GDPR consent policy is enabled. Markieren Sie die Cookies in den Tests als unverzichtbar, um nicht erforderliche cookies beizubehalten, wie z. B. diejenigen, die vom TempData-Anbieter verwendet werden.To preserve non-essential cookies, such as those used by the TempData provider, mark them as essential in your tests. Anweisungen zum Markieren eines cookies als erforderlich finden Sie unter Erforderliche cookies.For instructions on marking a cookie as essential, see Essential cookies.

Anpassen von WebApplicationFactoryCustomize WebApplicationFactory

Die Webhostkonfiguration kann unabhängig von den Testklassen durch Erben von der WebApplicationFactory erstellt werden, um eine oder mehrere benutzerdefinierte Factorys zu erstellen:Web host configuration can be created independently of the test classes by inheriting from WebApplicationFactory to create one or more custom factories:

  1. Erben Sie von WebApplicationFactory, und überschreiben Sie ConfigureWebHost.Inherit from WebApplicationFactory and override ConfigureWebHost. Der IWebHostBuilder erlaubt die Konfiguration der Dienstsammlung mit ConfigureServices:The IWebHostBuilder allows the configuration of the service collection with ConfigureServices:

    public class CustomWebApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType ==
                        typeof(DbContextOptions<ApplicationDbContext>));
    
                services.Remove(descriptor);
    
                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                });
    
                var sp = services.BuildServiceProvider();
    
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    db.Database.EnsureCreated();
    
                    try
                    {
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the " +
                            "database with test messages. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Das Datenbankseeding in der Beispiel-App wird mithilfe der InitializeDbForTests-Methode durchgeführt.Database seeding in the sample app is performed by the InitializeDbForTests method. Die Methode wird im Abschnitt Beispiel für Integrationstests: Organisation der Test-App beschrieben.The method is described in the Integration tests sample: Test app organization section.

    Der Datenbankkontext des GS wird in dessen Startup.ConfigureServices-Methode registriert.The SUT's database context is registered in its Startup.ConfigureServices method. Der builder.ConfigureServices-Rückruf der Test-App wird ausgeführt, nachdem der Startup.ConfigureServices-Code der App ausgeführt wurde.The test app's builder.ConfigureServices callback is executed after the app's Startup.ConfigureServices code is executed. Die Ausführungsreihenfolge ist im Release von ASP.NET Core 3.0 eine Breaking Change für den generischen Host.The execution order is a breaking change for the Generic Host with the release of ASP.NET Core 3.0. Um für die Tests eine andere Datenbank als die Datenbank der App zu verwenden, muss der Datenbankkontext der App in builder.ConfigureServices ersetzt werden.To use a different database for the tests than the app's database, the app's database context must be replaced in builder.ConfigureServices.

    Für GS, die weiterhin den Webhost verwenden, wird der builder.ConfigureServices-Rückruf der Test-App ausgeführt, bevor der Startup.ConfigureServices-Code des GS ausgeführt wird.For SUTs that still use the Web Host, the test app's builder.ConfigureServices callback is executed before the SUT's Startup.ConfigureServices code. Der builder.ConfigureTestServices-Rückruf der Test-App wird danach ausgeführt.The test app's builder.ConfigureTestServices callback is executed after.

    Die Beispiel-App findet den Dienstdeskriptor für den Datenbankkontext und verwendet den Deskriptor, um die Dienstregistrierung zu entfernen.The sample app finds the service descriptor for the database context and uses the descriptor to remove the service registration. Als Nächstes fügt die Factory einen neuen ApplicationDbContext hinzu, der eine In-Memory-Datenbank für die Tests verwendet.Next, the factory adds a new ApplicationDbContext that uses an in-memory database for the tests.

    Um eine Verbindung mit einer anderen Datenbank als der In-Memory-Datenbank herzustellen, ändern Sie den UseInMemoryDatabase-Aufruf, um den Kontext mit einer anderen Datenbank zu verbinden.To connect to a different database than the in-memory database, change the UseInMemoryDatabase call to connect the context to a different database. So verwenden Sie eine SQL Server-Testdatenbank:To use a SQL Server test database:

    services.AddDbContext<ApplicationDbContext>((options, context) => 
    {
        context.UseSqlServer(
            Configuration.GetConnectionString("TestingDbConnectionString"));
    });
    
  2. Verwenden Sie die benutzerdefinierte CustomWebApplicationFactory in Testklassen.Use the custom CustomWebApplicationFactory in test classes. Das folgende Beispiel verwendet die Factory in der IndexPageTests-Klasse:The following example uses the factory in the IndexPageTests class:

    public class IndexPageTests : 
        IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
        }
    

    Der Client der Beispiel-App wird so konfiguriert, dass der HttpClient keinen Umleitungen folgt.The sample app's client is configured to prevent the HttpClient from following redirects. Wie weiter unten im Abschnitt Pseudoauthentifizierung erläutert wird, können Tests so das Ergebnis der ersten Reaktion der App überprüfen.As explained later in the Mock authentication section, this permits tests to check the result of the app's first response. In vielen dieser Tests mit einem Location-Header ist die erste Antwort eine Umleitung.The first response is a redirect in many of these tests with a Location header.

  3. Ein typischer Test verwendet die HttpClient- und die Hilfsprogrammmethoden, um die Anforderung und die Antwort zu verarbeiten:A typical test uses the HttpClient and helper methods to process the request and the response:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

Jede POST-Anforderung an das GS muss die Fälschungsschutzprüfung bestehen, die automatisch vom Daten- und Fälschungsschutzsystem der App durchgeführt wird.Any POST request to the SUT must satisfy the antiforgery check that's automatically made by the app's data protection antiforgery system. Als Vorbereitung auf die POST-Anforderung eines Tests muss die Test-App folgende Schritte ausführen:In order to arrange for a test's POST request, the test app must:

  1. Senden einer Anforderung für die Seite.Make a request for the page.
  2. Analysieren des Fälschungsschutzcookies und des Anforderungsvalidierungstokens von der Antwort.Parse the antiforgery cookie and request validation token from the response.
  3. Senden der POST-Anforderung mit vorhandenem Fälschungsschutzcookie und Anforderungsvalidierungstoken.Make the POST request with the antiforgery cookie and request validation token in place.

Die SendAsync-Hilfsprogrammerweiterungsmethoden (Helpers/HttpClientExtensions.cs) und die GetDocumentAsync-Hilfsprogrammmethode (Helpers/HtmlHelpers.cs) in der Beispiel-App verwenden den AngleSharp-Parser, um die Fälschungsschutzprüfungen mit den folgenden Methoden durchzuführen:The SendAsync helper extension methods (Helpers/HttpClientExtensions.cs) and the GetDocumentAsync helper method (Helpers/HtmlHelpers.cs) in the sample app use the AngleSharp parser to handle the antiforgery check with the following methods:

  • GetDocumentAsync: Empfängt die HttpResponseMessage und gibt ein IHtmlDocument zurück.GetDocumentAsync: Receives the HttpResponseMessage and returns an IHtmlDocument. GetDocumentAsync verwendet eine Factory, die eine virtuelle Antwort basierend auf der ursprünglichen HttpResponseMessage vorbereitet.GetDocumentAsync uses a factory that prepares a virtual response based on the original HttpResponseMessage. Weitere Informationen finden Sie in der AngleSharp-Dokumentation.For more information, see the AngleSharp documentation.
  • SendAsync-Erweiterungsmethoden für den HttpClient verfassen eine HttpRequestMessage und rufen SendAsync(HttpRequestMessage) auf, um Anforderungen an das GS zu senden.SendAsync extension methods for the HttpClient compose an HttpRequestMessage and call SendAsync(HttpRequestMessage) to submit requests to the SUT. Überladungen für SendAsync akzeptieren das HTML-Formular (IHtmlFormElement) und Folgendes:Overloads for SendAsync accept the HTML form (IHtmlFormElement) and the following:
    • Schaltfläche „Senden“ des Formulars (IHtmlElement)Submit button of the form (IHtmlElement)
    • Formularwerteauflistung (IEnumerable<KeyValuePair<string, string>>)Form values collection (IEnumerable<KeyValuePair<string, string>>)
    • Schaltfläche „Senden“ (IHtmlElement) und Formularwerte (IEnumerable<KeyValuePair<string, string>>)Submit button (IHtmlElement) and form values (IEnumerable<KeyValuePair<string, string>>)

Hinweis

AngleSharp ist eine Drittanbieter-Analysebibliothek, die in diesem Thema und in der Beispiel-App zu Demonstrationszwecken verwendet wird.AngleSharp is a third-party parsing library used for demonstration purposes in this topic and the sample app. AngleSharp wird für Integrationstests von ASP.NET Core-Apps weder unterstützt noch benötigt.AngleSharp isn't supported or required for integration testing of ASP.NET Core apps. Andere Parser können verwendet werden, beispielsweise Html Agility Pack (HAP).Other parsers can be used, such as the Html Agility Pack (HAP). Ein anderer Ansatz besteht darin, Code zu schreiben, der das Anforderungsüberprüfungstoken und das Fälschungsschutzcookie des Fälschungsschutzsystems direkt verarbeitet.Another approach is to write code to handle the antiforgery system's request verification token and antiforgery cookie directly.

Anpassen des Clients mit WithWebHostBuilderCustomize the client with WithWebHostBuilder

Wenn eine zusätzliche Konfiguration innerhalb einer Testmethode erforderlich ist, erstellt WithWebHostBuilder eine neue WebApplicationFactory mit einem IWebHostBuilder, der mittels Konfiguration weiter angepasst wird.When additional configuration is required within a test method, WithWebHostBuilder creates a new WebApplicationFactory with an IWebHostBuilder that is further customized by configuration.

Die Post_DeleteMessageHandler_ReturnsRedirectToRoot-Testmethode der Beispiel-App zeigt die Verwendung von WithWebHostBuilder.The Post_DeleteMessageHandler_ReturnsRedirectToRoot test method of the sample app demonstrates the use of WithWebHostBuilder. Bei diesem Test wird eine Datensatzlöschung in der Datenbank durch Auslösen einer Formularübermittlung im GS durchführt.This test performs a record delete in the database by triggering a form submission in the SUT.

Da ein anderer Test in der IndexPageTests-Klasse einen Vorgang durchführt, der alle Datensätze in der Datenbank löscht und der möglicherweise vor der Post_DeleteMessageHandler_ReturnsRedirectToRoot-Methode ausgeführt wird, wird in dieser Testmethode ein erneutes Seeding der Datenbank durchgeführt, um sicherzustellen, dass ein Datensatz vorhanden ist, den das GS löschen kann.Because another test in the IndexPageTests class performs an operation that deletes all of the records in the database and may run before the Post_DeleteMessageHandler_ReturnsRedirectToRoot method, the database is reseeded in this test method to ensure that a record is present for the SUT to delete. Die Auswahl der ersten Löschschaltfläche des messages-Formulars im GS wird in der Anforderung an das GS simuliert:Selecting the first delete button of the messages form in the SUT is simulated in the request to the SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();

                using (var scope = serviceProvider.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices
                        .GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<IndexPageTests>>();

                    try
                    {
                        Utilities.ReinitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding " +
                            "the database with test messages. Error: {Message}", 
                            ex.Message);
                    }
                }
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

ClientoptionenClient options

In der folgenden Tabelle werden die Standard-WebApplicationFactoryClientOptions gezeigt, die bei der Erstellung von HttpClient-Instanzen verfügbar sind.The following table shows the default WebApplicationFactoryClientOptions available when creating HttpClient instances.

OptionOption BeschreibungDescription StandardDefault
AllowAutoRedirectAllowAutoRedirect Ruft ab oder legt fest, ob HttpClient-Instanzen automatisch Umleitungsantworten befolgen sollen.Gets or sets whether or not HttpClient instances should automatically follow redirect responses. true
BaseAddressBaseAddress Ruft die Basisadresse der HttpClient-Instanzen ab oder legt sie fest.Gets or sets the base address of HttpClient instances. http://localhost
HandlesCookieHandleCookies Ruft ab oder legt fest, ob HttpClient-Instanzen cookies verarbeiten sollen.Gets or sets whether HttpClient instances should handle cookies. true
MaxAutomaticRedirectionsMaxAutomaticRedirections Ruft die maximale Anzahl von Umleitungsantworten ab, die von HttpClient-Instanzen befolgt werden sollen, oder legt diese fest.Gets or sets the maximum number of redirect responses that HttpClient instances should follow. 77

Erstellen Sie die WebApplicationFactoryClientOptions-Klasse, und übergeben Sie sie an die CreateClient-Methode (Standardwerte werden im Codebeispiel gezeigt):Create the WebApplicationFactoryClientOptions class and pass it to the CreateClient method (default values are shown in the code example):

// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;

_client = _factory.CreateClient(clientOptions);

Fügen Sie Pseudodienste einInject mock services

Dienste können in einem Test mit dem Aufruf ConfigureTestServices auf dem Host-Builder überschrieben werden.Services can be overridden in a test with a call to ConfigureTestServices on the host builder. Um Pseudodienste einzufügen, muss das GS über eine Startup-Klasse mit einer Startup.ConfigureServices-Methode verfügen.To inject mock services, the SUT must have a Startup class with a Startup.ConfigureServices method.

Das Beispiel-GS enthält einen bereichsbezogenen Dienst, der ein Zitat zurückgibt.The sample SUT includes a scoped service that returns a quote. Wenn die Indexseite angefordert wird, wird das Zitat in ein ausgeblendetes Feld auf der Indexseite eingebettet.The quote is embedded in a hidden field on the Index page when the Index page is requested.

Services/IQuoteService.cs:Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Startup.cs:Startup.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

Das folgende Markup wird generiert, wenn die GS-App ausgeführt wird:The following markup is generated when the SUT app is run:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

Um den Dienst und die Zitateinfügung in einem Integrationstest zu testen, wird vom Test ein Pseudodienst in das GS eingefügt.To test the service and quote injection in an integration test, a mock service is injected into the SUT by the test. Der Pseudodienst ersetzt QuoteService der App durch einen Dienst namens TestQuoteService, der von der Test-App bereitgestellt wird:The mock service replaces the app's QuoteService with a service provided by the test app, called TestQuoteService:

IntegrationTests.IndexPageTests.cs:IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices wird aufgerufen, und der bereichsbezogene Dienst wird registriert:ConfigureTestServices is called, and the scoped service is registered:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

Das während der Ausführung des Tests erstellte Markup gibt das von TestQuoteService bereitgestellte Zitat wieder, somit ist die Assertion erfolgreich:The markup produced during the test's execution reflects the quote text supplied by TestQuoteService, thus the assertion passes:

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Pseudo-AuthentifizierungMock authentication

Tests in der AuthTests-Klasse prüfen, ob ein sicherer Endpunkt:Tests in the AuthTests class check that a secure endpoint:

  • Einen nicht authentifizierten Benutzer an die Anmeldeseite der App zurückleitet.Redirects an unauthenticated user to the app's Login page.
  • Den Inhalt für einen authentifizierten Benutzer zurückgibt.Returns content for an authenticated user.

Im GS verwendet die /SecurePage-Seite eine AuthorizePage-Konvention, um einen AuthorizeFilter auf die Seite anzuwenden.In the SUT, the /SecurePage page uses an AuthorizePage convention to apply an AuthorizeFilter to the page. Weitere Informationen finden Sie unter Razor Pages-Autorisierungskonventionen.For more information, see Razor Pages authorization conventions.

services.AddRazorPages(options =>
{
    options.Conventions.AuthorizePage("/SecurePage");
});

Im Get_SecurePageRedirectsAnUnauthenticatedUser-Test werden WebApplicationFactoryClientOptions so festgelegt, dass Umleitungen unzulässig sind, indem AllowAutoRedirect auf false festgelegt wird:In the Get_SecurePageRedirectsAnUnauthenticatedUser test, a WebApplicationFactoryClientOptions is set to disallow redirects by setting AllowAutoRedirect to false:

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login", 
        response.Headers.Location.OriginalString);
}

Indem dem Client untersagt wird, die Umleitung zu befolgen, können folgende Prüfungen durchgeführt werden:By disallowing the client to follow the redirect, the following checks can be made:

  • Der vom GS zurückgegebene Statuscode kann mit dem erwarteten HttpStatusCode.Redirect-Ergebnis verglichen werden – anstatt mit dem endgültigen Statuscode nach der Umleitung zur Anmeldeseite (HttpStatusCode.OK).The status code returned by the SUT can be checked against the expected HttpStatusCode.Redirect result, not the final status code after the redirect to the Login page, which would be HttpStatusCode.OK.
  • Der Wert für den Location-Header in den Antwortheadern wird geprüft, um zu bestätigen, dass er mit http://localhost/Identity/Account/Login beginnt. (Es wird nicht die abschließende Antwort der Anmeldeseite verwendet, bei der der Location-Header nicht vorhanden wäre.)The Location header value in the response headers is checked to confirm that it starts with http://localhost/Identity/Account/Login, not the final Login page response, where the Location header wouldn't be present.

Die Test-App kann einen AuthenticationHandler<TOptions> in ConfigureTestServices simulieren, um Aspekte der Authentifizierung und Autorisierung zu testen.The test app can mock an AuthenticationHandler<TOptions> in ConfigureTestServices in order to test aspects of authentication and authorization. Ein minimales Szenario gibt AuthenticateResult.Success zurück:A minimal scenario returns an AuthenticateResult.Success:

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

TestAuthHandler wird aufgerufen, um einen Benutzer zu authentifizieren, wenn das Authentifizierungsschema auf Test festgelegt wird, in dem AddAuthentication für ConfigureTestServices registriert ist.The TestAuthHandler is called to authenticate a user when the authentication scheme is set to Test where AddAuthentication is registered for ConfigureTestServices. Es ist wichtig, dass das Test-Schema mit dem Schema übereinstimmt, das Ihre App erwartet.It's important for the Test scheme to match the scheme your app expects. Andernfalls funktioniert die Authentifizierung nicht.Otherwise, authentication won't work.

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "Test", options => {});
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Test");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Weitere Informationen zu WebApplicationFactoryClientOptions finden Sie im Abschnitt Clientoptionen.For more information on WebApplicationFactoryClientOptions, see the Client options section.

Festlegen der UmgebungSet the environment

Standardmäßig ist die Host- und App-Umgebung des GS für die Verwendung der Entwicklungsumgebung konfiguriert.By default, the SUT's host and app environment is configured to use the Development environment. So überschreiben Sie die Umgebung des GS bei Verwendung von IHostBuilder:To override the SUT's environment when using IHostBuilder:

  • Legen Sie die ASPNETCORE_ENVIRONMENT-Umgebungsvariable fest (z. B. Staging, Production oder ein anderer benutzerdefinierter Wert wie Testing).Set the ASPNETCORE_ENVIRONMENT environment variable (for example, Staging, Production, or other custom value, such as Testing).
  • Überschreiben Sie CreateHostBuilder in der Test-App, um Umgebungsvariablen mit dem Präfix ASPNETCORE zu lesen.Override CreateHostBuilder in the test app to read environment variables prefixed with ASPNETCORE.
protected override IHostBuilder CreateHostBuilder() =>
    base.CreateHostBuilder()
        .ConfigureHostConfiguration(
            config => config.AddEnvironmentVariables("ASPNETCORE"));

Wenn das GS den Webhost (IWebHostBuilder) verwendet, überschreiben Sie CreateWebHostBuilder:If the SUT uses the Web Host (IWebHostBuilder), override CreateWebHostBuilder:

protected override IWebHostBuilder CreateWebHostBuilder() =>
    base.CreateWebHostBuilder().UseEnvironment("Testing");

Ableitung des Inhaltsstammpfads der App durch die TestinfrastrukturHow the test infrastructure infers the app content root path

Der WebApplicationFactory-Konstruktor leitet den Inhaltsstammpfad der App ab, indem er in der Assembly, die die Integrationstests enthält, nach einem WebApplicationFactoryContentRootAttribute mit einem Schlüssel sucht, der TEntryPoint assembly System.Reflection.Assembly.FullName entspricht.The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. Wenn kein Attribut mit dem richtigen Schlüssel gefunden wird, greift WebApplicationFactory auf die Suche nach einer Projektmappendatei ( .sln) zurück und fügt den TEntryPoint-Assemblynamen an das Projektmappenverzeichnis an.In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (.sln) and appends the TEntryPoint assembly name to the solution directory. Das Stammverzeichnis der App (der Inhaltsstammpfad) wird verwendet, um Sichten und Inhaltsdateien zu ermitteln.The app root directory (the content root path) is used to discover views and content files.

Deaktivieren der Erstellung von SchattenkopienDisable shadow copying

Das Erstellen von Schattenkopien bewirkt, dass die Tests in einem anderen Verzeichnis als dem Ausgabeverzeichnis ausgeführt werden.Shadow copying causes the tests to execute in a different directory than the output directory. Damit Tests ordnungsgemäß funktionieren, muss die Erstellung von Schattenkopien deaktiviert werden.For tests to work properly, shadow copying must be disabled. Die Beispiel-App verwendet xUnit und deaktiviert die Erstellung von Schattenkopien für xUnit durch Einschließen einer xunit.runner.json-Datei mit der korrekten Konfigurationseinstellung.The sample app uses xUnit and disables shadow copying for xUnit by including an xunit.runner.json file with the correct configuration setting. Weitere Informationen finden Sie unter Konfigurieren von xUnit mit JSON.For more information, see Configuring xUnit with JSON.

Fügen Sie die Datei xunit.runner.json mit folgendem Inhalt zum Stamm des Testprojekts hinzu:Add the xunit.runner.json file to root of the test project with the following content:

{
  "shadowCopy": false
}

Verwerfen von ObjektenDisposal of objects

Nachdem die Tests der IClassFixture-Implementierung ausgeführt wurden, werden TestServer und HttpClient verworfen, wenn xUnit die WebApplicationFactory verwirft.After the tests of the IClassFixture implementation are executed, TestServer and HttpClient are disposed when xUnit disposes of the WebApplicationFactory. Wenn vom Entwickler instanziierte Objekte verworfen werden müssen, müssen Sie dies in der IClassFixture-Implementierung tun.If objects instantiated by the developer require disposal, dispose of them in the IClassFixture implementation. Weitere Informationen finden Sie unter Implementieren einer Dispose-Methode.For more information, see Implementing a Dispose method.

Beispiel für IntegrationstestsIntegration tests sample

Die Beispiel-App besteht aus zwei Apps:The sample app is composed of two apps:

AppApp ProjektverzeichnisProject directory BeschreibungDescription
Nachrichten-App (das GS)Message app (the SUT) src/RazorPagesProjectsrc/RazorPagesProject Ermöglicht einem Benutzer, Nachrichten hinzuzufügen, eine oder alle Nachrichten zu löschen und Nachrichten zu analysieren.Allows a user to add, delete one, delete all, and analyze messages.
Testen der AppTest app tests/RazorPagesProject.Teststests/RazorPagesProject.Tests Wird für den Integrationstest des GS verwendet.Used to integration test the SUT.

Die Tests können mit den integrierten Testfunktionen einer IDE, wie z. B. Visual Studio ausgeführt werden.The tests can be run using the built-in test features of an IDE, such as Visual Studio. Wenn Sie Visual Studio Code oder die Befehlszeile verwenden, führen Sie den folgenden Befehl über eine Eingabeaufforderung im Verzeichnis tests/RazorPagesProject.Tests aus:If using Visual Studio Code or the command line, execute the following command at a command prompt in the tests/RazorPagesProject.Tests directory:

dotnet test

Organisation der Nachrichten-App (GS)Message app (SUT) organization

Beim GS handelt es sich um ein Razor Pages-Nachrichtensystem mit folgenden Merkmalen:The SUT is a Razor Pages message system with the following characteristics:

  • Die Indexseite der App (Pages/Index.cshtml und Pages/Index.cshtml.cs) stellt eine Benutzeroberfläche und Seitenmodellmethoden bereit, mit denen Sie das Hinzufügen, Löschen und Analysieren von Nachrichten (durchschnittliche Anzahl von Wörtern pro Nachricht) steuern können.The Index page of the app (Pages/Index.cshtml and Pages/Index.cshtml.cs) provides a UI and page model methods to control the addition, deletion, and analysis of messages (average words per message).
  • Eine Nachricht wird von der Message-Klasse (Data/Message.cs) mit zwei Eigenschaften beschrieben: Id (Schlüssel) und Text (Nachricht).A message is described by the Message class (Data/Message.cs) with two properties: Id (key) and Text (message). Die Text-Eigenschaft ist erforderlich und auf 200 Zeichen beschränkt.The Text property is required and limited to 200 characters.
  • Nachrichten werden mithilfe der In-Memory-Datenbank von Entity Framework† gespeichert.Messages are stored using Entity Framework's in-memory database†.
  • Die App enthält eine Datenzugriffsebene (DAL) in ihrer Datenbankkontextklasse AppDbContext (Data/AppDbContext.cs).The app contains a data access layer (DAL) in its database context class, AppDbContext (Data/AppDbContext.cs).
  • Wenn die Datenbank beim Starten der App leer ist, wird der Nachrichtenspeicher mit drei Nachrichten initialisiert.If the database is empty on app startup, the message store is initialized with three messages.
  • Die App enthält eine /SecurePage, auf die nur ein authentifizierter Benutzer zugreifen kann.The app includes a /SecurePage that can only be accessed by an authenticated user.

†Im Entity Framework-Thema Testen mit InMemory wird die Verwendung einer In-Memory-Datenbank für Tests mit MSTest erläutert.†The EF topic, Test with InMemory, explains how to use an in-memory database for tests with MSTest. In diesem Thema wird das Testframework xUnit verwendet.This topic uses the xUnit test framework. Testkonzepte und Testimplementierungen in verschiedenen Testframeworks sind ähnlich, jedoch nicht identisch.Test concepts and test implementations across different test frameworks are similar but not identical.

Obwohl die App nicht das Repositorymuster verwendet und kein effektives Beispiel für das Arbeitseinheitsmuster ist, unterstützt Razor Pages diese Entwicklungsmuster.Although the app doesn't use the repository pattern and isn't an effective example of the Unit of Work (UoW) pattern, Razor Pages supports these patterns of development. Weitere Informationen finden Sie unter Entwerfen der Persistenzebene der Infrastruktur und Testcontrollerlogik (im Beispiel wird das Repositorymuster implementiert).For more information, see Designing the infrastructure persistence layer and Test controller logic (the sample implements the repository pattern).

Organisation der Test-AppTest app organization

Bei der Test-App handelt es sich um eine Konsolen-App im Verzeichnis tests/RazorPagesProject.Tests.The test app is a console app inside the tests/RazorPagesProject.Tests directory.

Test-App-VerzeichnisTest app directory BeschreibungDescription
AuthTestsAuthTests Enthält Testmethoden für Folgendes:Contains test methods for:
  • Zugreifen auf eine sichere Seite durch einen nicht authentifizierten Benutzer.Accessing a secure page by an unauthenticated user.
  • Zugreifen auf eine sichere Seite durch einen authentifizierten Benutzer mit einem Pseudo-AuthenticationHandler<TOptions>.Accessing a secure page by an authenticated user with a mock AuthenticationHandler<TOptions>.
  • Abrufen eines GitHub-Benutzerprofils und Überprüfen der Benutzeranmeldung des Profils.Obtaining a GitHub user profile and checking the profile's user login.
BasicTestsBasicTests Enthält eine Testmethode für Routing und Inhaltstyp.Contains a test method for routing and content type.
IntegrationTestsIntegrationTests Enthält die Integrationstests für die Indexseite unter Verwendung der benutzerdefinierten WebApplicationFactory-Klasse.Contains the integration tests for the Index page using custom WebApplicationFactory class.
Helpers/UtilitiesHelpers/Utilities
  • Utilities.cs enthält die InitializeDbForTests-Methode, mit der ein Seeding der Datenbank mit Testdaten durchgeführt wird.Utilities.cs contains the InitializeDbForTests method used to seed the database with test data.
  • HtmlHelpers.cs stellt eine Methode bereit, mit der ein AngleSharp-IHtmlDocument zur Verwendung durch die Testmethoden zurückgegeben wird.HtmlHelpers.cs provides a method to return an AngleSharp IHtmlDocument for use by the test methods.
  • HttpClientExtensions.cs stellt Überladungen für SendAsync bereit, um Anforderungen an das GS zu senden.HttpClientExtensions.cs provide overloads for SendAsync to submit requests to the SUT.

Das Testframework ist xUnit.The test framework is xUnit. Integrationstests werden mit dem Microsoft.AspNetCore.TestHost durchgeführt, der den Testserver umfasst.Integration tests are conducted using the Microsoft.AspNetCore.TestHost, which includes the TestServer. Da das Microsoft.AspNetCore.Mvc.Testing-Paket zum Konfigurieren des Testhosts und des Testservers verwendet wird, benötigen die TestHost- und TestServer-Pakete keine direkten Paketverweise in der Projektdatei der Test-App bzw. keine Entwicklerkonfiguration in der Test-App.Because the Microsoft.AspNetCore.Mvc.Testing package is used to configure the test host and test server, the TestHost and TestServer packages don't require direct package references in the test app's project file or developer configuration in the test app.

Seeding der Datenbank für TestzweckeSeeding the database for testing

Für Integrationstests muss die Datenbank in der Regel vor der Testausführung ein kleines Dataset enthalten.Integration tests usually require a small dataset in the database prior to the test execution. Beispielsweise wird bei einem Löschtest ein Löschvorgang eines Datensatzes der Datenbank abgerufen, weshalb die Datenbank mindestens einen Datensatz aufweisen muss, damit die Löschanforderung erfolgreich ausgeführt wird.For example, a delete test calls for a database record deletion, so the database must have at least one record for the delete request to succeed.

Die Beispiel-App führt ein Seeding der Datenbank mit drei Nachrichten in Utilities.cs durch, die von Tests bei der Ausführung verwendet werden können:The sample app seeds the database with three messages in Utilities.cs that tests can use when they execute:

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

Der Datenbankkontext des GS wird in dessen Startup.ConfigureServices-Methode registriert.The SUT's database context is registered in its Startup.ConfigureServices method. Der builder.ConfigureServices-Rückruf der Test-App wird ausgeführt, nachdem der Startup.ConfigureServices-Code der App ausgeführt wurde.The test app's builder.ConfigureServices callback is executed after the app's Startup.ConfigureServices code is executed. Um eine andere Datenbank für die Tests zu verwenden, muss der Datenbankkontext der App in builder.ConfigureServices ersetzt werden.To use a different database for the tests, the app's database context must be replaced in builder.ConfigureServices. Weitere Informationen finden Sie im Abschnitt Anpassen von WebApplicationFactory.For more information, see the Customize WebApplicationFactory section.

Für GS, die weiterhin den Webhost verwenden, wird der builder.ConfigureServices-Rückruf der Test-App ausgeführt, bevor der Startup.ConfigureServices-Code des GS ausgeführt wird.For SUTs that still use the Web Host, the test app's builder.ConfigureServices callback is executed before the SUT's Startup.ConfigureServices code. Der builder.ConfigureTestServices-Rückruf der Test-App wird danach ausgeführt.The test app's builder.ConfigureTestServices callback is executed after.

Integrationstests stellen sicher, dass die Komponenten einer App auf einer Ebene, die die unterstützende Infrastruktur der App (wie die Datenbank, das Dateisystem und das Netzwerk) umfasst, ordnungsgemäß funktionieren.Integration tests ensure that an app's components function correctly at a level that includes the app's supporting infrastructure, such as the database, file system, and network. ASP.NET Core unterstützt Integrationstests mithilfe eines Komponententest-Frameworks mit einem Testwebhost und einem In-Memory-Testserver.ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server.

In diesem Thema werden Grundkenntnisse über Komponententests vorausgesetzt.This topic assumes a basic understanding of unit tests. Wenn Sie nicht mit Testkonzepten vertraut sind, lesen Sie das Thema Komponententests in .NET Core und .NET Standard und zugehörige Inhalte.If unfamiliar with test concepts, see the Unit Testing in .NET Core and .NET Standard topic and its linked content.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)View or download sample code (how to download)

Bei der Beispiel-App handelt es sich um eine Razor Pages-App, hierfür werden grundlegende Kenntnisse über Razor Pages vorausgesetzt.The sample app is a Razor Pages app and assumes a basic understanding of Razor Pages. Wenn Sie nicht mit Razor Pages vertraut sind, lesen Sie die folgenden Themen:If unfamiliar with Razor Pages, see the following topics:

Hinweis

Zum Testen von Single-Page-Webanwendungen empfiehlt es sich, ein Tool wie Selenium zu verwenden, mit dem ein Browser automatisiert werden kann.For testing SPAs, we recommended a tool such as Selenium, which can automate a browser.

Einführung in IntegrationstestsIntroduction to integration tests

Integrationstests bewerten die Komponenten einer App auf breiterer Ebene als Komponententests.Integration tests evaluate an app's components on a broader level than unit tests. Komponententests werden verwendet, um isolierte Softwarekomponenten wie z. B. einzelne Klassenmethoden zu testen.Unit tests are used to test isolated software components, such as individual class methods. Integrationstests bestätigen, dass zwei oder mehr App-Komponenten zusammenarbeiten, um ein erwartetes Ergebnis zu erzielen, ggf. auch unter Einbindung aller Komponenten, die für die vollständige Verarbeitung einer Anforderung erforderlich sind.Integration tests confirm that two or more app components work together to produce an expected result, possibly including every component required to fully process a request.

Diese umfassenderen Tests werden verwendet, um die Infrastruktur und das gesamte Framework der App zu testen, häufig einschließlich der folgenden Komponenten:These broader tests are used to test the app's infrastructure and whole framework, often including the following components:

  • DatenbankDatabase
  • DateisystemFile system
  • Netzwerk-AppliancesNetwork appliances
  • Anforderung/Antwort-PipelineRequest-response pipeline

Komponententests verwenden anstelle von Infrastrukturkomponenten künstliche Komponenten, die als Fakes oder Pseudoobjekte bezeichnet werden.Unit tests use fabricated components, known as fakes or mock objects, in place of infrastructure components.

Im Gegensatz zu Komponententests gilt für Integrationstests:In contrast to unit tests, integration tests:

  • Sie verwenden die tatsächlichen Komponenten, die von der App in der Produktionsumgebung verwendet werden.Use the actual components that the app uses in production.
  • Sie erfordern mehr Code und Datenverarbeitung.Require more code and data processing.
  • Ihre Ausführung dauert länger.Take longer to run.

Beschränken Sie daher die Verwendung von Integrationstests auf die wichtigsten Infrastrukturszenarios.Therefore, limit the use of integration tests to the most important infrastructure scenarios. Wenn ein Verhalten mithilfe eines Komponententests oder eines Integrationstests getestet werden kann, wählen Sie den Komponententest.If a behavior can be tested using either a unit test or an integration test, choose the unit test.

Tipp

Schreiben Sie keine Integrationstests für jede denkbare Permutation des Daten- und Dateizugriffs bei Datenbanken und Dateisystemen.Don't write integration tests for every possible permutation of data and file access with databases and file systems. Unabhängig davon, wie viele Elemente in einer App mit Datenbanken und Dateisystemen interagieren, ist ein fokussierter Satz von Lese-, Schreib-, Update- und Lösch-Integrationstests üblicherweise in der Lage, die Datenbank- und Dateisystemkomponenten adäquat zu testen.Regardless of how many places across an app interact with databases and file systems, a focused set of read, write, update, and delete integration tests are usually capable of adequately testing database and file system components. Verwenden Sie Komponententests für Routinetests der Methodenlogik, die mit diesen Komponenten interagieren.Use unit tests for routine tests of method logic that interact with these components. Bei Komponententests führt die Verwendung von Infrastruktur-Fakes/-Pseudoobjekten zu einer schnelleren Testausführung.In unit tests, the use of infrastructure fakes/mocks result in faster test execution.

Hinweis

Bei der Besprechung von Integrationstests wird das getestete Projekt häufig als getestetes System oder kurz „GS“ bezeichnet.In discussions of integration tests, the tested project is frequently called the System Under Test, or "SUT" for short.

In diesem Thema wird „GS“ verwendet, um auf die getestete ASP.NET Core-App zu verweisen."SUT" is used throughout this topic to refer to the tested ASP.NET Core app.

Integrationstests in ASP.NET CoreASP.NET Core integration tests

Integrationstests in ASP.NET Core erfordern Folgendes:Integration tests in ASP.NET Core require the following:

  • Es wird ein Testprojekt verwendet, um die Tests einzugrenzen und auszuführen.A test project is used to contain and execute the tests. Das Testprojekt enthält einen Verweis auf das GS.The test project has a reference to the SUT.
  • Das Testprojekt erstellt einen Testwebhost für das GS und verwendet einen Testserverclient, um Anforderungen und Antworten im Zusammenhang mit dem GS zu verarbeiten.The test project creates a test web host for the SUT and uses a test server client to handle requests and responses with the SUT.
  • Um die Tests auszuführen und die Testergebnisse zu melden, wird ein Test-Runner verwendet.A test runner is used to execute the tests and report the test results.

Integrationstests folgen einer Sequenz von Ereignissen, die die üblichen Testschritte Arrange, Act und Assert umfassen:Integration tests follow a sequence of events that include the usual Arrange, Act, and Assert test steps:

  1. Der Webhost des GS wird konfiguriert.The SUT's web host is configured.
  2. Es wird ein Testserverclient erstellt, um Anforderungen an die App zu senden.A test server client is created to submit requests to the app.
  3. Der Testschritt Arrange wird ausgeführt: Die Test-App bereitet eine Anforderung vor.The Arrange test step is executed: The test app prepares a request.
  4. Der Testschritt Act wird ausgeführt: Der Client sendet die Anforderung und empfängt die Antwort.The Act test step is executed: The client submits the request and receives the response.
  5. Der Testschritt Assert wird ausgeführt: Die tatsächliche Antwort wird je nach der erwarteten Antwort als Pass oder Fail bewertet.The Assert test step is executed: The actual response is validated as a pass or fail based on an expected response.
  6. Der Prozess wird so lange fortgesetzt, bis alle Tests ausgeführt wurden.The process continues until all of the tests are executed.
  7. Die Testergebnisse werden gemeldet.The test results are reported.

Üblicherweise ist der Testwebhost anders konfiguriert als der normale Webhost der App für die Testläufe.Usually, the test web host is configured differently than the app's normal web host for the test runs. Beispielsweise könnten für die Tests eine andere Datenbank oder andere App-Einstellungen verwendet werden.For example, a different database or different app settings might be used for the tests.

Infrastrukturkomponenten wie der Testwebhost und der In-Memory-Testserver (TestServer) werden durch das Microsoft.AspNetCore.Mvc.Testing-Paket bereitgestellt oder verwaltet.Infrastructure components, such as the test web host and in-memory test server (TestServer), are provided or managed by the Microsoft.AspNetCore.Mvc.Testing package. Durch die Verwendung dieses Pakets werden die Testerstellung und -ausführung optimiert.Use of this package streamlines test creation and execution.

Das Microsoft.AspNetCore.Mvc.Testing-Paket verarbeitet die folgenden Aufgaben:The Microsoft.AspNetCore.Mvc.Testing package handles the following tasks:

  • Es kopiert die Datei für Abhängigkeiten ( .deps) aus dem GS in das bin- Verzeichnis des Testprojekts.Copies the dependencies file (.deps) from the SUT into the test project's bin directory.
  • Es legt das Inhaltsstammelement auf das Projektstammelement des GS fest, damit statische Dateien und Seiten/Ansichten bei der Ausführung der Tests gefunden werden.Sets the content root to the SUT's project root so that static files and pages/views are found when the tests are executed.
  • Es stellt die Klasse WebApplicationFactory zur Optimierung des Bootstrappings des GS mit TestServer bereit.Provides the WebApplicationFactory class to streamline bootstrapping the SUT with TestServer.

In der Dokumentation der Komponententests wird beschrieben, wie Sie ein Testprojekt und einen Test-Runner einrichten. Ferner finden Sie dort ausführliche Anweisungen zum Ausführen von Tests sowie Empfehlungen zum Benennen von Tests und Testklassen.The unit tests documentation describes how to set up a test project and test runner, along with detailed instructions on how to run tests and recommendations for how to name tests and test classes.

Hinweis

Wenn Sie ein Testprojekt für eine App erstellen, verwenden Sie unterschiedliche Projekte für die Komponententests und die Integrationstests.When creating a test project for an app, separate the unit tests from the integration tests into different projects. Dadurch wird sichergestellt, dass Komponenten für Infrastrukturtests nicht versehentlich in die Komponententests eingeschlossen werden.This helps ensure that infrastructure testing components aren't accidentally included in the unit tests. Durch die Trennung von Komponenten- und Integrationstests können Sie außerdem steuern, welche Testsätze ausgeführt werden.Separation of unit and integration tests also allows control over which set of tests are run.

Es gibt praktisch keinen Unterschied zwischen der Konfiguration für Tests von Razor Pages-Apps und MVC-Apps.There's virtually no difference between the configuration for tests of Razor Pages apps and MVC apps. Der einzige Unterschied besteht darin, wie die Tests benannt werden.The only difference is in how the tests are named. In einer Razor Pages-App werden Tests von Seitenendpunkten normalerweise nach der Seitenmodellklasse benannt (z. B. IndexPageTests für das Testen der Komponentenintegration für die Indexseite).In a Razor Pages app, tests of page endpoints are usually named after the page model class (for example, IndexPageTests to test component integration for the Index page). In einer MVC-App werden Tests in der Regel nach Controllerklassen organisiert und nach den von ihnen getesteten Controllern benannt (z. B. HomeControllerTests für das Testen der Komponentenintegration für den Startseitencontroller).In an MVC app, tests are usually organized by controller classes and named after the controllers they test (for example, HomeControllerTests to test component integration for the Home controller).

Voraussetzungen für Test-AppsTest app prerequisites

Für das Testprojekt muss Folgendes erfüllt sein:The test project must:

Diese Voraussetzungen können Sie in der Beispiel-App sehen.These prerequisites can be seen in the sample app. Untersuchen Sie die Datei tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj.Inspect the tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj file. Die Beispiel-App verwendet das xUnit-Testframework und die AngleSharp-Parserbibliothek, sodass die Beispiel-APP auch auf Folgendes verweist:The sample app uses the xUnit test framework and the AngleSharp parser library, so the sample app also references:

GS-UmgebungSUT environment

Wenn die Umgebung des GS nicht festgelegt ist, wird standardmäßig die Entwicklungsumgebung verwendet.If the SUT's environment isn't set, the environment defaults to Development.

Grundlegende Tests mit der Standard-WebApplicationFactoryBasic tests with the default WebApplicationFactory

WebApplicationFactory<TEntryPoint> wird verwendet, um einen TestServer für die Integrationstests zu erstellen.WebApplicationFactory<TEntryPoint> is used to create a TestServer for the integration tests. TEntryPoint ist die Einstiegspunktklasse des GS, in der Regel die Startup-Klasse.TEntryPoint is the entry point class of the SUT, usually the Startup class.

Testklassen implementieren eine class fixture-Schnittstelle (IClassFixture), um anzugeben, dass die Klasse Tests enthält und um in den Tests in der Klasse gemeinsame Objektinstanzen bereitzustellen.Test classes implement a class fixture interface (IClassFixture) to indicate the class contains tests and provide shared object instances across the tests in the class.

Die folgende Testklasse (BasicTests) verwendet die WebApplicationFactory für den Bootstrap des GS und um einen HttpClient für eine Testmethode Get_EndpointsReturnSuccessAndCorrectContentType bereitzustellen.The following test class, BasicTests, uses the WebApplicationFactory to bootstrap the SUT and provide an HttpClient to a test method, Get_EndpointsReturnSuccessAndCorrectContentType. Die Methode prüft, ob der Antwortstatuscode erfolgreich ist (Statuscodes im Bereich 200-299) und der Content-Type-Header für mehrere App-Seiten text/html; charset=utf-8 lautet.The method checks if the response status code is successful (status codes in the range 200-299) and the Content-Type header is text/html; charset=utf-8 for several app pages.

CreateClient erstellt eine Instanz von HttpClient, die automatisch Umleitungen folgt und cookies verarbeitet.CreateClient creates an instance of HttpClient that automatically follows redirects and handles cookies.

public class BasicTests 
    : IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;

    public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/Index")]
    [InlineData("/About")]
    [InlineData("/Privacy")]
    [InlineData("/Contact")]
    public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
    {
        // Arrange
        var client = _factory.CreateClient();

        // Act
        var response = await client.GetAsync(url);

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.Equal("text/html; charset=utf-8", 
            response.Content.Headers.ContentType.ToString());
    }
}

Standardmäßig werden nicht erforderliche cookies nicht über Anforderungen hinweg beibehalten, wenn die DSGVO-Zustimmungsrichtlinie aktiviert ist.By default, non-essential cookies aren't preserved across requests when the GDPR consent policy is enabled. Markieren Sie die Cookies in den Tests als unverzichtbar, um nicht erforderliche cookies beizubehalten, wie z. B. diejenigen, die vom TempData-Anbieter verwendet werden.To preserve non-essential cookies, such as those used by the TempData provider, mark them as essential in your tests. Anweisungen zum Markieren eines cookies als erforderlich finden Sie unter Erforderliche cookies.For instructions on marking a cookie as essential, see Essential cookies.

Anpassen von WebApplicationFactoryCustomize WebApplicationFactory

Die Webhostkonfiguration kann unabhängig von den Testklassen durch Erben von der WebApplicationFactory erstellt werden, um eine oder mehrere benutzerdefinierte Factorys zu erstellen:Web host configuration can be created independently of the test classes by inheriting from WebApplicationFactory to create one or more custom factories:

  1. Erben Sie von WebApplicationFactory, und überschreiben Sie ConfigureWebHost.Inherit from WebApplicationFactory and override ConfigureWebHost. Der IWebHostBuilder erlaubt die Konfiguration der Dienstsammlung mit ConfigureServices, die vor den Startup.ConfigureServices der App ausgeführt werden.The IWebHostBuilder allows the configuration of the service collection with ConfigureServices, which is executed before the app's Startup.ConfigureServices. Der IWebHostBuilder ermöglicht das Überschreiben und Ändern registrierter Dienste in der Dienstsammlung mit ConfigureTestServices:The IWebHostBuilder allows overriding and modifying registered services in the service collection with ConfigureTestServices:

    public class CustomWebApplicationFactory<TStartup> 
        : WebApplicationFactory<TStartup> where TStartup: class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();
    
                // Add a database context (ApplicationDbContext) using an in-memory 
                // database for testing.
                services.AddDbContext<ApplicationDbContext>(options => 
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                    options.UseInternalServiceProvider(serviceProvider);
                });
    
                // Build the service provider.
                var sp = services.BuildServiceProvider();
    
                // Create a scope to obtain a reference to the database
                // context (ApplicationDbContext).
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
    
                    // Ensure the database is created.
                    db.Database.EnsureCreated();
    
                    try
                    {
                        // Seed the database with test data.
                        Utilities.InitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding the database. Error: {Message}", ex.Message);
                    }
                }
            });
        }
    }
    

    Das Datenbankseeding in der Beispiel-App wird mithilfe der InitializeDbForTests-Methode durchgeführt.Database seeding in the sample app is performed by the InitializeDbForTests method. Die Methode wird im Abschnitt Beispiel für Integrationstests: Organisation der Test-App beschrieben.The method is described in the Integration tests sample: Test app organization section.

  2. Verwenden Sie die benutzerdefinierte CustomWebApplicationFactory in Testklassen.Use the custom CustomWebApplicationFactory in test classes. Das folgende Beispiel verwendet die Factory in der IndexPageTests-Klasse:The following example uses the factory in the IndexPageTests class:

    public class IndexPageTests : 
        IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
    {
        private readonly HttpClient _client;
        private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
            _factory;
    
        public IndexPageTests(
            CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient(new WebApplicationFactoryClientOptions
                {
                    AllowAutoRedirect = false
                });
        }
    

    Der Client der Beispiel-App wird so konfiguriert, dass der HttpClient keinen Umleitungen folgt.The sample app's client is configured to prevent the HttpClient from following redirects. Wie weiter unten im Abschnitt Pseudoauthentifizierung erläutert wird, können Tests so das Ergebnis der ersten Reaktion der App überprüfen.As explained later in the Mock authentication section, this permits tests to check the result of the app's first response. In vielen dieser Tests mit einem Location-Header ist die erste Antwort eine Umleitung.The first response is a redirect in many of these tests with a Location header.

  3. Ein typischer Test verwendet die HttpClient- und die Hilfsprogrammmethoden, um die Anforderung und die Antwort zu verarbeiten:A typical test uses the HttpClient and helper methods to process the request and the response:

    [Fact]
    public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
    {
        // Arrange
        var defaultPage = await _client.GetAsync("/");
        var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    
        //Act
        var response = await _client.SendAsync(
            (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
            (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']"));
    
        // Assert
        Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
        Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
        Assert.Equal("/", response.Headers.Location.OriginalString);
    }
    

Jede POST-Anforderung an das GS muss die Fälschungsschutzprüfung bestehen, die automatisch vom Daten- und Fälschungsschutzsystem der App durchgeführt wird.Any POST request to the SUT must satisfy the antiforgery check that's automatically made by the app's data protection antiforgery system. Als Vorbereitung auf die POST-Anforderung eines Tests muss die Test-App folgende Schritte ausführen:In order to arrange for a test's POST request, the test app must:

  1. Senden einer Anforderung für die Seite.Make a request for the page.
  2. Analysieren des Fälschungsschutzcookies und des Anforderungsvalidierungstokens von der Antwort.Parse the antiforgery cookie and request validation token from the response.
  3. Senden der POST-Anforderung mit vorhandenem Fälschungsschutzcookie und Anforderungsvalidierungstoken.Make the POST request with the antiforgery cookie and request validation token in place.

Die SendAsync-Hilfsprogrammerweiterungsmethoden (Helpers/HttpClientExtensions.cs) und die GetDocumentAsync-Hilfsprogrammmethode (Helpers/HtmlHelpers.cs) in der Beispiel-App verwenden den AngleSharp-Parser, um die Fälschungsschutzprüfungen mit den folgenden Methoden durchzuführen:The SendAsync helper extension methods (Helpers/HttpClientExtensions.cs) and the GetDocumentAsync helper method (Helpers/HtmlHelpers.cs) in the sample app use the AngleSharp parser to handle the antiforgery check with the following methods:

  • GetDocumentAsync: Empfängt die HttpResponseMessage und gibt ein IHtmlDocument zurück.GetDocumentAsync: Receives the HttpResponseMessage and returns an IHtmlDocument. GetDocumentAsync verwendet eine Factory, die eine virtuelle Antwort basierend auf der ursprünglichen HttpResponseMessage vorbereitet.GetDocumentAsync uses a factory that prepares a virtual response based on the original HttpResponseMessage. Weitere Informationen finden Sie in der AngleSharp-Dokumentation.For more information, see the AngleSharp documentation.
  • SendAsync-Erweiterungsmethoden für den HttpClient verfassen eine HttpRequestMessage und rufen SendAsync(HttpRequestMessage) auf, um Anforderungen an das GS zu senden.SendAsync extension methods for the HttpClient compose an HttpRequestMessage and call SendAsync(HttpRequestMessage) to submit requests to the SUT. Überladungen für SendAsync akzeptieren das HTML-Formular (IHtmlFormElement) und Folgendes:Overloads for SendAsync accept the HTML form (IHtmlFormElement) and the following:
    • Schaltfläche „Senden“ des Formulars (IHtmlElement)Submit button of the form (IHtmlElement)
    • Formularwerteauflistung (IEnumerable<KeyValuePair<string, string>>)Form values collection (IEnumerable<KeyValuePair<string, string>>)
    • Schaltfläche „Senden“ (IHtmlElement) und Formularwerte (IEnumerable<KeyValuePair<string, string>>)Submit button (IHtmlElement) and form values (IEnumerable<KeyValuePair<string, string>>)

Hinweis

AngleSharp ist eine Drittanbieter-Analysebibliothek, die in diesem Thema und in der Beispiel-App zu Demonstrationszwecken verwendet wird.AngleSharp is a third-party parsing library used for demonstration purposes in this topic and the sample app. AngleSharp wird für Integrationstests von ASP.NET Core-Apps weder unterstützt noch benötigt.AngleSharp isn't supported or required for integration testing of ASP.NET Core apps. Andere Parser können verwendet werden, beispielsweise Html Agility Pack (HAP).Other parsers can be used, such as the Html Agility Pack (HAP). Ein anderer Ansatz besteht darin, Code zu schreiben, der das Anforderungsüberprüfungstoken und das Fälschungsschutzcookie des Fälschungsschutzsystems direkt verarbeitet.Another approach is to write code to handle the antiforgery system's request verification token and antiforgery cookie directly.

Anpassen des Clients mit WithWebHostBuilderCustomize the client with WithWebHostBuilder

Wenn eine zusätzliche Konfiguration innerhalb einer Testmethode erforderlich ist, erstellt WithWebHostBuilder eine neue WebApplicationFactory mit einem IWebHostBuilder, der mittels Konfiguration weiter angepasst wird.When additional configuration is required within a test method, WithWebHostBuilder creates a new WebApplicationFactory with an IWebHostBuilder that is further customized by configuration.

Die Post_DeleteMessageHandler_ReturnsRedirectToRoot-Testmethode der Beispiel-App zeigt die Verwendung von WithWebHostBuilder.The Post_DeleteMessageHandler_ReturnsRedirectToRoot test method of the sample app demonstrates the use of WithWebHostBuilder. Bei diesem Test wird eine Datensatzlöschung in der Datenbank durch Auslösen einer Formularübermittlung im GS durchführt.This test performs a record delete in the database by triggering a form submission in the SUT.

Da ein anderer Test in der IndexPageTests-Klasse einen Vorgang durchführt, der alle Datensätze in der Datenbank löscht und der möglicherweise vor der Post_DeleteMessageHandler_ReturnsRedirectToRoot-Methode ausgeführt wird, wird in dieser Testmethode ein erneutes Seeding der Datenbank durchgeführt, um sicherzustellen, dass ein Datensatz vorhanden ist, den das GS löschen kann.Because another test in the IndexPageTests class performs an operation that deletes all of the records in the database and may run before the Post_DeleteMessageHandler_ReturnsRedirectToRoot method, the database is reseeded in this test method to ensure that a record is present for the SUT to delete. Die Auswahl der ersten Löschschaltfläche des messages-Formulars im GS wird in der Anforderung an das GS simuliert:Selecting the first delete button of the messages form in the SUT is simulated in the request to the SUT:

[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();

                using (var scope = serviceProvider.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices
                        .GetRequiredService<ApplicationDbContext>();
                    var logger = scopedServices
                        .GetRequiredService<ILogger<IndexPageTests>>();

                    try
                    {
                        Utilities.ReinitializeDbForTests(db);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "An error occurred seeding " +
                            "the database with test messages. Error: {Message}" +
                            ex.Message);
                    }
                }
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);

    //Act
    var response = await client.SendAsync(
        (IHtmlFormElement)content.QuerySelector("form[id='messages']"),
        (IHtmlButtonElement)content.QuerySelector("form[id='messages']")
            .QuerySelector("div[class='panel-body']")
            .QuerySelector("button"));

    // Assert
    Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.Equal("/", response.Headers.Location.OriginalString);
}

ClientoptionenClient options

In der folgenden Tabelle werden die Standard-WebApplicationFactoryClientOptions gezeigt, die bei der Erstellung von HttpClient-Instanzen verfügbar sind.The following table shows the default WebApplicationFactoryClientOptions available when creating HttpClient instances.

OptionOption BESCHREIBUNGDescription StandardDefault
AllowAutoRedirectAllowAutoRedirect Ruft ab oder legt fest, ob HttpClient-Instanzen automatisch Umleitungsantworten befolgen sollen.Gets or sets whether or not HttpClient instances should automatically follow redirect responses. true
BaseAddressBaseAddress Ruft die Basisadresse der HttpClient-Instanzen ab oder legt sie fest.Gets or sets the base address of HttpClient instances. http://localhost
HandlesCookieHandleCookies Ruft ab oder legt fest, ob HttpClient-Instanzen cookies verarbeiten sollen.Gets or sets whether HttpClient instances should handle cookies. true
MaxAutomaticRedirectionsMaxAutomaticRedirections Ruft die maximale Anzahl von Umleitungsantworten ab, die von HttpClient-Instanzen befolgt werden sollen, oder legt diese fest.Gets or sets the maximum number of redirect responses that HttpClient instances should follow. 77

Erstellen Sie die WebApplicationFactoryClientOptions-Klasse, und übergeben Sie sie an die CreateClient-Methode (Standardwerte werden im Codebeispiel gezeigt):Create the WebApplicationFactoryClientOptions class and pass it to the CreateClient method (default values are shown in the code example):

// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;

_client = _factory.CreateClient(clientOptions);

Fügen Sie Pseudodienste einInject mock services

Dienste können in einem Test mit dem Aufruf ConfigureTestServices auf dem Host-Builder überschrieben werden.Services can be overridden in a test with a call to ConfigureTestServices on the host builder. Um Pseudodienste einzufügen, muss das GS über eine Startup-Klasse mit einer Startup.ConfigureServices-Methode verfügen.To inject mock services, the SUT must have a Startup class with a Startup.ConfigureServices method.

Das Beispiel-GS enthält einen bereichsbezogenen Dienst, der ein Zitat zurückgibt.The sample SUT includes a scoped service that returns a quote. Wenn die Indexseite angefordert wird, wird das Zitat in ein ausgeblendetes Feld auf der Indexseite eingebettet.The quote is embedded in a hidden field on the Index page when the Index page is requested.

Services/IQuoteService.cs:Services/IQuoteService.cs:

public interface IQuoteService
{
    Task<string> GenerateQuote();
}

Services/QuoteService.cs:Services/QuoteService.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Come on, Sarah. We've an appointment in London, " +
            "and we're already 30,000 years late.");
    }
}

Startup.cs:Startup.cs:

services.AddScoped<IQuoteService, QuoteService>();

Pages/Index.cshtml.cs:Pages/Index.cshtml.cs:

public class IndexModel : PageModel
{
    private readonly ApplicationDbContext _db;
    private readonly IQuoteService _quoteService;

    public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
    {
        _db = db;
        _quoteService = quoteService;
    }

    [BindProperty]
    public Message Message { get; set; }

    public IList<Message> Messages { get; private set; }

    [TempData]
    public string MessageAnalysisResult { get; set; }

    public string Quote { get; private set; }

    public async Task OnGetAsync()
    {
        Messages = await _db.GetMessagesAsync();

        Quote = await _quoteService.GenerateQuote();
    }

Pages/Index.cs:Pages/Index.cs:

<input id="quote" type="hidden" value="@Model.Quote">

Das folgende Markup wird generiert, wenn die GS-App ausgeführt wird:The following markup is generated when the SUT app is run:

<input id="quote" type="hidden" value="Come on, Sarah. We&#x27;ve an appointment in 
    London, and we&#x27;re already 30,000 years late.">

Um den Dienst und die Zitateinfügung in einem Integrationstest zu testen, wird vom Test ein Pseudodienst in das GS eingefügt.To test the service and quote injection in an integration test, a mock service is injected into the SUT by the test. Der Pseudodienst ersetzt QuoteService der App durch einen Dienst namens TestQuoteService, der von der Test-App bereitgestellt wird:The mock service replaces the app's QuoteService with a service provided by the test app, called TestQuoteService:

IntegrationTests.IndexPageTests.cs:IntegrationTests.IndexPageTests.cs:

// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
    public Task<string> GenerateQuote()
    {
        return Task.FromResult<string>(
            "Something's interfering with time, Mr. Scarman, " +
            "and time is my business.");
    }
}

ConfigureTestServices wird aufgerufen, und der bereichsbezogene Dienst wird registriert:ConfigureTestServices is called, and the scoped service is registered:

[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddScoped<IQuoteService, TestQuoteService>();
            });
        })
        .CreateClient();

    //Act
    var defaultPage = await client.GetAsync("/");
    var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
    var quoteElement = content.QuerySelector("#quote");

    // Assert
    Assert.Equal("Something's interfering with time, Mr. Scarman, " +
        "and time is my business.", quoteElement.Attributes["value"].Value);
}

Das während der Ausführung des Tests erstellte Markup gibt das von TestQuoteService bereitgestellte Zitat wieder, somit ist die Assertion erfolgreich:The markup produced during the test's execution reflects the quote text supplied by TestQuoteService, thus the assertion passes:

<input id="quote" type="hidden" value="Something&#x27;s interfering with time, 
    Mr. Scarman, and time is my business.">

Pseudo-AuthentifizierungMock authentication

Tests in der AuthTests-Klasse prüfen, ob ein sicherer Endpunkt:Tests in the AuthTests class check that a secure endpoint:

  • Einen nicht authentifizierten Benutzer an die Anmeldeseite der App zurückleitet.Redirects an unauthenticated user to the app's Login page.
  • Den Inhalt für einen authentifizierten Benutzer zurückgibt.Returns content for an authenticated user.

Im GS verwendet die /SecurePage-Seite eine AuthorizePage-Konvention, um einen AuthorizeFilter auf die Seite anzuwenden.In the SUT, the /SecurePage page uses an AuthorizePage convention to apply an AuthorizeFilter to the page. Weitere Informationen finden Sie unter Razor Pages-Autorisierungskonventionen.For more information, see Razor Pages authorization conventions.

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AuthorizePage("/SecurePage");
    });

Im Get_SecurePageRedirectsAnUnauthenticatedUser-Test werden WebApplicationFactoryClientOptions so festgelegt, dass Umleitungen unzulässig sind, indem AllowAutoRedirect auf false festgelegt wird:In the Get_SecurePageRedirectsAnUnauthenticatedUser test, a WebApplicationFactoryClientOptions is set to disallow redirects by setting AllowAutoRedirect to false:

[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
    // Arrange
    var client = _factory.CreateClient(
        new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });

    // Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
    Assert.StartsWith("http://localhost/Identity/Account/Login", 
        response.Headers.Location.OriginalString);
}

Indem dem Client untersagt wird, die Umleitung zu befolgen, können folgende Prüfungen durchgeführt werden:By disallowing the client to follow the redirect, the following checks can be made:

  • Der vom GS zurückgegebene Statuscode kann mit dem erwarteten HttpStatusCode.Redirect-Ergebnis verglichen werden – anstatt mit dem endgültigen Statuscode nach der Umleitung zur Anmeldeseite (HttpStatusCode.OK).The status code returned by the SUT can be checked against the expected HttpStatusCode.Redirect result, not the final status code after the redirect to the Login page, which would be HttpStatusCode.OK.
  • Der Wert für den Location-Header in den Antwortheadern wird geprüft, um zu bestätigen, dass er mit http://localhost/Identity/Account/Login beginnt. (Es wird nicht die abschließende Antwort der Anmeldeseite verwendet, bei der der Location-Header nicht vorhanden wäre.)The Location header value in the response headers is checked to confirm that it starts with http://localhost/Identity/Account/Login, not the final Login page response, where the Location header wouldn't be present.

Die Test-App kann einen AuthenticationHandler<TOptions> in ConfigureTestServices simulieren, um Aspekte der Authentifizierung und Autorisierung zu testen.The test app can mock an AuthenticationHandler<TOptions> in ConfigureTestServices in order to test aspects of authentication and authorization. Ein minimales Szenario gibt AuthenticateResult.Success zurück:A minimal scenario returns an AuthenticateResult.Success:

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");

        var result = AuthenticateResult.Success(ticket);

        return Task.FromResult(result);
    }
}

Der TestAuthHandler wird aufgerufen, um einen Benutzer zu authentifizieren, wenn das Authentifizierungsschema auf Test festgelegt ist, in dem AddAuthentication für ConfigureTestServices registriert ist:The TestAuthHandler is called to authenticate a user when the authentication scheme is set to Test where AddAuthentication is registered for ConfigureTestServices:

[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
    // Arrange
    var client = _factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication("Test")
                    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                        "Test", options => {});
            });
        })
        .CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false,
        });

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Test");

    //Act
    var response = await client.GetAsync("/SecurePage");

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Weitere Informationen zu WebApplicationFactoryClientOptions finden Sie im Abschnitt Clientoptionen.For more information on WebApplicationFactoryClientOptions, see the Client options section.

Festlegen der UmgebungSet the environment

Standardmäßig ist die Host- und App-Umgebung des GS für die Verwendung der Entwicklungsumgebung konfiguriert.By default, the SUT's host and app environment is configured to use the Development environment. So überschreiben Sie die Umgebung des GS:To override the SUT's environment:

  • Legen Sie die ASPNETCORE_ENVIRONMENT-Umgebungsvariable fest (z. B. Staging, Production oder ein anderer benutzerdefinierter Wert wie Testing).Set the ASPNETCORE_ENVIRONMENT environment variable (for example, Staging, Production, or other custom value, such as Testing).
  • Überschreiben Sie CreateWebHostBuilder in der Test-App, um die ASPNETCORE_ENVIRONMENT-Umgebungsvariable zu lesen.Override CreateWebHostBuilder in the test app to read the ASPNETCORE_ENVIRONMENT environment variable.
public class CustomWebApplicationFactory<TStartup> 
    : WebApplicationFactory<TStartup> where TStartup: class
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return base.CreateWebHostBuilder()
            .UseEnvironment(
                Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
    }

    ...
}

Die Umgebung kann auch direkt auf dem Hostgenerator in einer benutzerdefinierten WebApplicationFactory<TEntryPoint> festgelegt werden:The environment can also be set directly on the host builder in a custom WebApplicationFactory<TEntryPoint>:

public class CustomWebApplicationFactory<TStartup> 
    : WebApplicationFactory<TStartup> where TStartup: class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment(
            Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));

        ...
    }

    ...

Ableitung des Inhaltsstammpfads der App durch die TestinfrastrukturHow the test infrastructure infers the app content root path

Der WebApplicationFactory-Konstruktor leitet den Inhaltsstammpfad der App ab, indem er in der Assembly, die die Integrationstests enthält, nach einem WebApplicationFactoryContentRootAttribute mit einem Schlüssel sucht, der TEntryPoint assembly System.Reflection.Assembly.FullName entspricht.The WebApplicationFactory constructor infers the app content root path by searching for a WebApplicationFactoryContentRootAttribute on the assembly containing the integration tests with a key equal to the TEntryPoint assembly System.Reflection.Assembly.FullName. Wenn kein Attribut mit dem richtigen Schlüssel gefunden wird, greift WebApplicationFactory auf die Suche nach einer Projektmappendatei ( .sln) zurück und fügt den TEntryPoint-Assemblynamen an das Projektmappenverzeichnis an.In case an attribute with the correct key isn't found, WebApplicationFactory falls back to searching for a solution file (.sln) and appends the TEntryPoint assembly name to the solution directory. Das Stammverzeichnis der App (der Inhaltsstammpfad) wird verwendet, um Sichten und Inhaltsdateien zu ermitteln.The app root directory (the content root path) is used to discover views and content files.

Deaktivieren der Erstellung von SchattenkopienDisable shadow copying

Das Erstellen von Schattenkopien bewirkt, dass die Tests in einem anderen Verzeichnis als dem Ausgabeverzeichnis ausgeführt werden.Shadow copying causes the tests to execute in a different directory than the output directory. Damit Tests ordnungsgemäß funktionieren, muss die Erstellung von Schattenkopien deaktiviert werden.For tests to work properly, shadow copying must be disabled. Die Beispiel-App verwendet xUnit und deaktiviert die Erstellung von Schattenkopien für xUnit durch Einschließen einer xunit.runner.json-Datei mit der korrekten Konfigurationseinstellung.The sample app uses xUnit and disables shadow copying for xUnit by including an xunit.runner.json file with the correct configuration setting. Weitere Informationen finden Sie unter Konfigurieren von xUnit mit JSON.For more information, see Configuring xUnit with JSON.

Fügen Sie die Datei xunit.runner.json mit folgendem Inhalt zum Stamm des Testprojekts hinzu:Add the xunit.runner.json file to root of the test project with the following content:

{
  "shadowCopy": false
}

Wenn Sie Visual Studio verwenden, legen Sie die Eigenschaft In Ausgabeverzeichnis kopieren der Datei auf Immer kopieren fest.If using Visual Studio, set the file's Copy to Output Directory property to Copy always. Wenn Sie Visual Studio nicht verwenden, fügen Sie der Projektdatei der Test-App ein Content-Ziel hinzu:If not using Visual Studio, add a Content target to the test app's project file:

<ItemGroup>
  <Content Update="xunit.runner.json">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Verwerfen von ObjektenDisposal of objects

Nachdem die Tests der IClassFixture-Implementierung ausgeführt wurden, werden TestServer und HttpClient verworfen, wenn xUnit die WebApplicationFactory verwirft.After the tests of the IClassFixture implementation are executed, TestServer and HttpClient are disposed when xUnit disposes of the WebApplicationFactory. Wenn vom Entwickler instanziierte Objekte verworfen werden müssen, müssen Sie dies in der IClassFixture-Implementierung tun.If objects instantiated by the developer require disposal, dispose of them in the IClassFixture implementation. Weitere Informationen finden Sie unter Implementieren einer Dispose-Methode.For more information, see Implementing a Dispose method.

Beispiel für IntegrationstestsIntegration tests sample

Die Beispiel-App besteht aus zwei Apps:The sample app is composed of two apps:

AppApp ProjektverzeichnisProject directory BeschreibungDescription
Nachrichten-App (das GS)Message app (the SUT) src/RazorPagesProjectsrc/RazorPagesProject Ermöglicht einem Benutzer, Nachrichten hinzuzufügen, eine oder alle Nachrichten zu löschen und Nachrichten zu analysieren.Allows a user to add, delete one, delete all, and analyze messages.
Testen der AppTest app tests/RazorPagesProject.Teststests/RazorPagesProject.Tests Wird für den Integrationstest des GS verwendet.Used to integration test the SUT.

Die Tests können mit den integrierten Testfunktionen einer IDE, wie z. B. Visual Studio ausgeführt werden.The tests can be run using the built-in test features of an IDE, such as Visual Studio. Wenn Sie Visual Studio Code oder die Befehlszeile verwenden, führen Sie den folgenden Befehl über eine Eingabeaufforderung im Verzeichnis tests/RazorPagesProject.Tests aus:If using Visual Studio Code or the command line, execute the following command at a command prompt in the tests/RazorPagesProject.Tests directory:

dotnet test

Organisation der Nachrichten-App (GS)Message app (SUT) organization

Beim GS handelt es sich um ein Razor Pages-Nachrichtensystem mit folgenden Merkmalen:The SUT is a Razor Pages message system with the following characteristics:

  • Die Indexseite der App (Pages/Index.cshtml und Pages/Index.cshtml.cs) stellt eine Benutzeroberfläche und Seitenmodellmethoden bereit, mit denen Sie das Hinzufügen, Löschen und Analysieren von Nachrichten (durchschnittliche Anzahl von Wörtern pro Nachricht) steuern können.The Index page of the app (Pages/Index.cshtml and Pages/Index.cshtml.cs) provides a UI and page model methods to control the addition, deletion, and analysis of messages (average words per message).
  • Eine Nachricht wird von der Message-Klasse (Data/Message.cs) mit zwei Eigenschaften beschrieben: Id (Schlüssel) und Text (Nachricht).A message is described by the Message class (Data/Message.cs) with two properties: Id (key) and Text (message). Die Text-Eigenschaft ist erforderlich und auf 200 Zeichen beschränkt.The Text property is required and limited to 200 characters.
  • Nachrichten werden mithilfe der In-Memory-Datenbank von Entity Framework† gespeichert.Messages are stored using Entity Framework's in-memory database†.
  • Die App enthält eine Datenzugriffsebene (DAL) in ihrer Datenbankkontextklasse AppDbContext (Data/AppDbContext.cs).The app contains a data access layer (DAL) in its database context class, AppDbContext (Data/AppDbContext.cs).
  • Wenn die Datenbank beim Starten der App leer ist, wird der Nachrichtenspeicher mit drei Nachrichten initialisiert.If the database is empty on app startup, the message store is initialized with three messages.
  • Die App enthält eine /SecurePage, auf die nur ein authentifizierter Benutzer zugreifen kann.The app includes a /SecurePage that can only be accessed by an authenticated user.

†Im Entity Framework-Thema Testen mit InMemory wird die Verwendung einer In-Memory-Datenbank für Tests mit MSTest erläutert.†The EF topic, Test with InMemory, explains how to use an in-memory database for tests with MSTest. In diesem Thema wird das Testframework xUnit verwendet.This topic uses the xUnit test framework. Testkonzepte und Testimplementierungen in verschiedenen Testframeworks sind ähnlich, jedoch nicht identisch.Test concepts and test implementations across different test frameworks are similar but not identical.

Obwohl die App nicht das Repositorymuster verwendet und kein effektives Beispiel für das Arbeitseinheitsmuster ist, unterstützt Razor Pages diese Entwicklungsmuster.Although the app doesn't use the repository pattern and isn't an effective example of the Unit of Work (UoW) pattern, Razor Pages supports these patterns of development. Weitere Informationen finden Sie unter Entwerfen der Persistenzebene der Infrastruktur und Testcontrollerlogik (im Beispiel wird das Repositorymuster implementiert).For more information, see Designing the infrastructure persistence layer and Test controller logic (the sample implements the repository pattern).

Organisation der Test-AppTest app organization

Bei der Test-App handelt es sich um eine Konsolen-App im Verzeichnis tests/RazorPagesProject.Tests.The test app is a console app inside the tests/RazorPagesProject.Tests directory.

Test-App-VerzeichnisTest app directory BeschreibungDescription
AuthTestsAuthTests Enthält Testmethoden für Folgendes:Contains test methods for:
  • Zugreifen auf eine sichere Seite durch einen nicht authentifizierten Benutzer.Accessing a secure page by an unauthenticated user.
  • Zugreifen auf eine sichere Seite durch einen authentifizierten Benutzer mit einem Pseudo-AuthenticationHandler<TOptions>.Accessing a secure page by an authenticated user with a mock AuthenticationHandler<TOptions>.
  • Abrufen eines GitHub-Benutzerprofils und Überprüfen der Benutzeranmeldung des Profils.Obtaining a GitHub user profile and checking the profile's user login.
BasicTestsBasicTests Enthält eine Testmethode für Routing und Inhaltstyp.Contains a test method for routing and content type.
IntegrationTestsIntegrationTests Enthält die Integrationstests für die Indexseite unter Verwendung der benutzerdefinierten WebApplicationFactory-Klasse.Contains the integration tests for the Index page using custom WebApplicationFactory class.
Helpers/UtilitiesHelpers/Utilities
  • Utilities.cs enthält die InitializeDbForTests-Methode, mit der ein Seeding der Datenbank mit Testdaten durchgeführt wird.Utilities.cs contains the InitializeDbForTests method used to seed the database with test data.
  • HtmlHelpers.cs stellt eine Methode bereit, mit der ein AngleSharp-IHtmlDocument zur Verwendung durch die Testmethoden zurückgegeben wird.HtmlHelpers.cs provides a method to return an AngleSharp IHtmlDocument for use by the test methods.
  • HttpClientExtensions.cs stellt Überladungen für SendAsync bereit, um Anforderungen an das GS zu senden.HttpClientExtensions.cs provide overloads for SendAsync to submit requests to the SUT.

Das Testframework ist xUnit.The test framework is xUnit. Integrationstests werden mit dem Microsoft.AspNetCore.TestHost durchgeführt, der den Testserver umfasst.Integration tests are conducted using the Microsoft.AspNetCore.TestHost, which includes the TestServer. Da das Microsoft.AspNetCore.Mvc.Testing-Paket zum Konfigurieren des Testhosts und des Testservers verwendet wird, benötigen die TestHost- und TestServer-Pakete keine direkten Paketverweise in der Projektdatei der Test-App bzw. keine Entwicklerkonfiguration in der Test-App.Because the Microsoft.AspNetCore.Mvc.Testing package is used to configure the test host and test server, the TestHost and TestServer packages don't require direct package references in the test app's project file or developer configuration in the test app.

Seeding der Datenbank für TestzweckeSeeding the database for testing

Für Integrationstests muss die Datenbank in der Regel vor der Testausführung ein kleines Dataset enthalten.Integration tests usually require a small dataset in the database prior to the test execution. Beispielsweise wird bei einem Löschtest ein Löschvorgang eines Datensatzes der Datenbank abgerufen, weshalb die Datenbank mindestens einen Datensatz aufweisen muss, damit die Löschanforderung erfolgreich ausgeführt wird.For example, a delete test calls for a database record deletion, so the database must have at least one record for the delete request to succeed.

Die Beispiel-App führt ein Seeding der Datenbank mit drei Nachrichten in Utilities.cs durch, die von Tests bei der Ausführung verwendet werden können:The sample app seeds the database with three messages in Utilities.cs that tests can use when they execute:

public static void InitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.AddRange(GetSeedingMessages());
    db.SaveChanges();
}

public static void ReinitializeDbForTests(ApplicationDbContext db)
{
    db.Messages.RemoveRange(db.Messages);
    InitializeDbForTests(db);
}

public static List<Message> GetSeedingMessages()
{
    return new List<Message>()
    {
        new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
        new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
        new Message(){ Text = "TEST RECORD: To the rational mind, " +
            "nothing is inexplicable; only unexplained." }
    };
}

Zusätzliche RessourcenAdditional resources