Komponententests für Razor Pages in ASP.NET CoreRazor Pages unit tests in ASP.NET Core

ASP.NET Core unterstützt Komponententests von Razor Pages-Apps.ASP.NET Core supports unit tests of Razor Pages apps. Tests der Datenzugriffsebene (Data Access Layer, DAL) und Seitenmodelle helfen dabei, Folgendes sicherzustellen:Tests of the data access layer (DAL) and page models help ensure:

  • Teile einer Razor Pages-App funktionieren während der App-Erstellung unabhängig und zusammen als Einheit.Parts of a Razor Pages app work independently and together as a unit during app construction.
  • Klassen und Methoden weisen eingeschränkte Zuständigkeitsbereiche auf.Classes and methods have limited scopes of responsibility.
  • Es gibt zusätzliche Dokumentation zum Verhalten der App.Additional documentation exists on how the app should behave.
  • Regressionen, bei denen es sich um Fehler bei Aktualisierungen des Codes handelt, werden während der automatisierten Erstellung und Bereitstellung gefunden.Regressions, which are errors brought about by updates to the code, are found during automated building and deployment.

In diesem Thema wird davon ausgegangen, dass Sie über grundlegende Kenntnisse über Razor Pages-Apps und -Komponententests verfügen.This topic assumes that you have a basic understanding of Razor Pages apps and unit tests. Wenn Sie mit Razor Pages-Apps oder Testkonzepten nicht vertraut sind, finden Sie weitere Informationen in den folgenden Themen:If you're unfamiliar with Razor Pages apps or test concepts, see the following topics:

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

Das Beispielprojekt besteht aus zwei Apps:The sample project is composed of two apps:

AppApp ProjektordnerProject folder BeschreibungDescription
Nachrichten-AppMessage app src/RazorPagesTestSamplesrc/RazorPagesTestSample Ermöglicht es dem Benutzer, eine Nachricht hinzuzufügen, eine Nachricht zu löschen, alle Nachrichten zu löschen und Nachrichten zu analysieren (durch Ermitteln der durchschnittlichen Anzahl von Wörtern pro Nachricht).Allows a user to add a message, delete one message, delete all messages, and analyze messages (find the average number of words per message).
Testen der AppTest app tests/RazorPagesTestSample.Teststests/RazorPagesTestSample.Tests Wird verwendet, um Komponententests für die DAL und das Indexseitenmodell der Nachrichten-App durchzusetzen.Used to unit test the DAL and Index page model of the message app.

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

dotnet test

Organisation der Nachrichten-AppMessage app organization

Bei der Nachrichten-App handelt es sich um ein Razor Pages-Nachrichtensystem mit folgenden Merkmalen:The message app 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 der Sie das Hinzufügen, Löschen und Analysieren von Nachrichten (durch Ermitteln der durchschnittlichen 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 (find the average number of 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 DAL in ihrer Datenbankkontext-Klasse, AppDbContext (Data/AppDbContext.cs).The app contains a DAL in its database context class, AppDbContext (Data/AppDbContext.cs). Die DAL-Methoden sind als virtual gekennzeichnet, sodass die Methoden für die Verwendung in den Tests simuliert werden können.The DAL methods are marked virtual, which allows mocking the methods for use in the tests.
  • 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. Diese per Seeding hinzugefügten Nachrichten werden auch in Tests verwendet.These seeded messages are also used in tests.

†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 Beispiel-App nicht das Repositorymuster verwendet und kein effektives Beispiel für das Arbeitseinheitsmuster ist, unterstützt Razor Pages diese Entwicklungsmuster.Although the sample 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 Testen von Controllerlogik in ASP.NET Core (im Beispiel wird das Repositorymuster implementiert).For more information, see Designing the infrastructure persistence layer and Testen von Controllerlogik in ASP.NET Core (the sample implements the repository pattern).

Organisation der Test-AppTest app organization

Bei der Test-App handelt es sich um eine Konsolen-App innerhalb des Ordners tests/RazorPagesTestSample.Tests.The test app is a console app inside the tests/RazorPagesTestSample.Tests folder.

Test-App-OrdnerTest app folder BeschreibungDescription
UnitTestsUnitTests
  • DataAccessLayerTest.cs enthält die Komponententests für die DAL.DataAccessLayerTest.cs contains the unit tests for the DAL.
  • IndexPageTests.cs enthält die Komponententests für das Indexseitenmodell.IndexPageTests.cs contains the unit tests for the Index page model.
UtilitiesUtilities Enthält die TestDbContextOptions-Methode, mit der neue Datenbankkontext-Optionen für jeden DAL-Komponententest erstellt werden, sodass die Datenbank für jeden Test auf ihren Ausgangszustand zurückgesetzt wird.Contains the TestDbContextOptions method used to create new database context options for each DAL unit test so that the database is reset to its baseline condition for each test.

Das Testframework ist xUnit.The test framework is xUnit. Das Framework für die Objektsimulation ist Moq.The object mocking framework is Moq.

Komponententests der Datenzugriffsebene (Data Access Layer, DAL)Unit tests of the data access layer (DAL)

Die Nachrichten-App verfügt über eine Datenzugriffsebene mit vier Methoden, die in der AppDbContext-Klasse (src/RazorPagesTestSample/Data/AppDbContext.cs) enthalten sind.The message app has a DAL with four methods contained in the AppDbContext class (src/RazorPagesTestSample/Data/AppDbContext.cs). Jede Methode verfügt über einen oder zwei Komponententests in der Test-App.Each method has one or two unit tests in the test app.

DAL-MethodeDAL method FunktionFunction
GetMessagesAsync Ruft eine nach der Eigenschaft Text sortierte List<Message> aus der Datenbank ab.Obtains a List<Message> from the database sorted by the Text property.
AddMessageAsync Fügt der Datenbank eine Message hinzu.Adds a Message to the database.
DeleteAllMessagesAsync Löscht alle Message-Einträge aus der Datenbank.Deletes all Message entries from the database.
DeleteMessageAsync Löscht eine einzelne, nach Id sortierte Message aus der Datenbank.Deletes a single Message from the database by Id.

Komponententests der DAL erfordern DbContextOptions, wenn für jeden Test ein neuer AppDbContext erstellt wird.Unit tests of the DAL require DbContextOptions when creating a new AppDbContext for each test. Ein Ansatz zum Erstellen der DbContextOptions für jeden Test ist die Verwendung eines DbContextOptionsBuilder:One approach to creating the DbContextOptions for each test is to use a DbContextOptionsBuilder:

var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
    .UseInMemoryDatabase("InMemoryDb");

using (var db = new AppDbContext(optionsBuilder.Options))
{
    // Use the db here in the unit test.
}

Das Problem bei diesem Ansatz besteht darin, dass jeder Test die Datenbank in dem Zustand empfängt, in dem sie sich im vorherigen Test zum Schluss befand.The problem with this approach is that each test receives the database in whatever state the previous test left it. Dies kann problematisch sein, wenn versucht wird, atomische Komponententests zu schreiben, die sich nicht einander beeinträchtigen.This can be problematic when trying to write atomic unit tests that don't interfere with each other. Um zu erzwingen, dass der AppDbContext für jeden Test einen neuen Datenbankkontext verwendet, stellen Sie eine DbContextOptions-Instanz bereit, die auf einem neuen Dienstanbieter basiert.To force the AppDbContext to use a new database context for each test, supply a DbContextOptions instance that's based on a new service provider. Die Test-App zeigt, wie dies mit der Utilities-Klassenmethode TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs) möglich ist:The test app shows how to do this using its Utilities class method TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs):

public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
    // Create a new service provider to create a new in-memory database.
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkInMemoryDatabase()
        .BuildServiceProvider();

    // Create a new options instance using an in-memory database and 
    // IServiceProvider that the context should resolve all of its 
    // services from.
    var builder = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("InMemoryDb")
        .UseInternalServiceProvider(serviceProvider);

    return builder.Options;
}

Wenn Sie die DbContextOptions in den DAL-Komponententests verwenden, kann jeder Test atomisch mit einer neuen Datenbankinstanz ausgeführt werden:Using the DbContextOptions in the DAL unit tests allows each test to run atomically with a fresh database instance:

using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
    // Use the db here in the unit test.
}

Jede Testmethode in der DataAccessLayerTest-Klasse (UnitTests/DataAccessLayerTest.cs) folgt einem ähnlichen „Arrange-Act-Assert“-Muster:Each test method in the DataAccessLayerTest class (UnitTests/DataAccessLayerTest.cs) follows a similar Arrange-Act-Assert pattern:

  1. „Arrange“ (Anordnen) Die Datenbank ist für den Test konfiguriert, und/oder das erwartete Ergebnis ist definiert.Arrange: The database is configured for the test and/or the expected outcome is defined.
  2. „Act“ (Aktion ausführen): Der Test wird ausgeführt.Act: The test is executed.
  3. Assert (Bestätigen): Es werden Assertionen erstellt, um zu bestimmen, ob das Testergebnis ein Erfolg ist.Assert: Assertions are made to determine if the test result is a success.

Beispielsweise ist die Methode DeleteMessageAsync für das Entfernen einer einzelnen Nachricht verantwortlich, die durch Id (src/RazorPagesTestSample/Data/AppDbContext.cs) identifiziert wird:For example, the DeleteMessageAsync method is responsible for removing a single message identified by its Id (src/RazorPagesTestSample/Data/AppDbContext.cs):

public async virtual Task DeleteMessageAsync(int id)
{
    var message = await Messages.FindAsync(id);

    if (message != null)
    {
        Messages.Remove(message);
        await SaveChangesAsync();
    }
}

Für diese Methode gibt es zwei Tests.There are two tests for this method. Ein Test prüft, ob die Methode eine Nachricht löscht, wenn die Nachricht in der Datenbank vorhanden ist.One test checks that the method deletes a message when the message is present in the database. Die andere Methode testet, ob die Datenbank nicht geändert wird, wenn die Nachrichten-Id zum Löschen nicht vorhanden ist.The other method tests that the database doesn't change if the message Id for deletion doesn't exist. Die DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound-Methode wird im folgenden Beispiel dargestellt:The DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound method is shown below:

[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
    using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
    {
        // Arrange
        var seedMessages = AppDbContext.GetSeedingMessages();
        await db.AddRangeAsync(seedMessages);
        await db.SaveChangesAsync();
        var recId = 1;
        var expectedMessages = 
            seedMessages.Where(message => message.Id != recId).ToList();

        // Act
        await db.DeleteMessageAsync(recId);

        // Assert
        var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
        Assert.Equal(
            expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
            actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
    }
}

Zuerst führt die Methode den Schritt „Arrange“ (Anordnen) aus, bei dem die Vorbereitung für den Schritt „Act“ (Aktion ausführen) stattfindet.First, the method performs the Arrange step, where preparation for the Act step takes place. Die Seeding-Nachrichten werden abgerufen und in seedMessages gespeichert.The seeding messages are obtained and held in seedMessages. Die Seeding-Nachrichten werden in der Datenbank gespeichert.The seeding messages are saved into the database. Die Nachricht mit einer Id 1 wird zum Löschen festgelegt.The message with an Id of 1 is set for deletion. Wenn die DeleteMessageAsync-Methode ausgeführt wird, sollten die erwarteten Nachrichten alle Nachrichten umfassen, mit Ausnahme derjenigen, die eine Id 1 aufweisen.When the DeleteMessageAsync method is executed, the expected messages should have all of the messages except for the one with an Id of 1. Die Variable expectedMessages stellt dieses erwartete Ergebnis dar.The expectedMessages variable represents this expected outcome.

// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages = 
    seedMessages.Where(message => message.Id != recId).ToList();

Die Methode verhält sich wie folgt: Die DeleteMessageAsync-Methode wird ausgeführt, indem die recId 1 übergeben wird:The method acts: The DeleteMessageAsync method is executed passing in the recId of 1:

// Act
await db.DeleteMessageAsync(recId);

Abschließend ruft die Methode die Messages aus dem Kontext ab und vergleicht sie mit den expectedMessages mit der Behauptung, dass die beiden identisch sind:Finally, the method obtains the Messages from the context and compares it to the expectedMessages asserting that the two are equal:

// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
    expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
    actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Um vergleichen zu können, ob die beiden List<Message> identisch sind, werden folgende Aktionen ausgeführt:In order to compare that the two List<Message> are the same:

  • Die Nachrichten werden nach Id sortiert.The messages are ordered by Id.
  • Nachrichtenpaare werden anhand der Text-Eigenschaft verglichen.Message pairs are compared on the Text property.

DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound, eine ähnliche Testmethode, überprüft das Ergebnis des Versuchs, eine Meldung zu löschen, die nicht vorhanden ist.A similar test method, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound checks the result of attempting to delete a message that doesn't exist. In diesem Fall sollten die erwarteten Nachrichten in der Datenbank mit den tatsächlichen Nachrichten übereinstimmen, nachdem die DeleteMessageAsync-Methode ausgeführt wurde.In this case, the expected messages in the database should be equal to the actual messages after the DeleteMessageAsync method is executed. Der Inhalt der Datenbank sollte nicht geändert werden:There should be no change to the database's content:

[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
    using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
    {
        // Arrange
        var expectedMessages = AppDbContext.GetSeedingMessages();
        await db.AddRangeAsync(expectedMessages);
        await db.SaveChangesAsync();
        var recId = 4;

        // Act
        try
        {
            await db.DeleteMessageAsync(recId);
        }
        catch
        {
            // recId doesn't exist
        }

        // Assert
        var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
        Assert.Equal(
            expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
            actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
    }
}

Komponententests der SeitenmodellmethodenUnit tests of the page model methods

Weitere Komponententests sind für Tests von Seitenmodellmethoden verantwortlich.Another set of unit tests is responsible for tests of page model methods. In der Nachrichten-App befinden sich die Indexseitenmodelle in der IndexModel-Klasse in src/RazorPagesTestSample/Pages/Index.cshtml.cs.In the message app, the Index page models are found in the IndexModel class in src/RazorPagesTestSample/Pages/Index.cshtml.cs.

SeitenmodellmethodePage model method FunktionFunction
OnGetAsync Ruft die Nachrichten aus der DAL für die Benutzeroberfläche mithilfe der GetMessagesAsync-Methode ab.Obtains the messages from the DAL for the UI using the GetMessagesAsync method.
OnPostAddMessageAsync Wenn der ModelState gültig ist, wird AddMessageAsync aufgerufen, um der Datenbank eine Nachricht hinzuzufügen.If the ModelState is valid, calls AddMessageAsync to add a message to the database.
OnPostDeleteAllMessagesAsync Ruft DeleteAllMessagesAsync auf, um alle Nachrichten in der Datenbank zu löschen.Calls DeleteAllMessagesAsync to delete all of the messages in the database.
OnPostDeleteMessageAsync Führt DeleteMessageAsync aus, um eine Meldung mit der angegebenen Id zu löschen.Executes DeleteMessageAsync to delete a message with the Id specified.
OnPostAnalyzeMessagesAsync Wenn sich eine oder mehrere Nachrichten in der Datenbank befinden, wird die durchschnittliche Anzahl von Wörtern pro Nachricht berechnet.If one or more messages are in the database, calculates the average number of words per message.

Die Seitenmodellmethoden werden mithilfe von sieben Tests in der IndexPageTests-Klasse (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs) getestet.The page model methods are tested using seven tests in the IndexPageTests class (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Bei den Tests wird das bekannte „Arrange-Act-Assert“-Muster verwendet.The tests use the familiar Arrange-Assert-Act pattern. Diese Tests konzentrieren sich auf folgende Punkte:These tests focus on:

  • Es wird ermittelt, ob die Methoden das richtige Verhalten aufweisen, wenn der ModelState ungültig ist.Determining if the methods follow the correct behavior when the ModelState is invalid.
  • Es wird überprüft, ob nach dem Bestätigen der Methode das richtige IActionResult erzeugt wird.Confirming the methods produce the correct IActionResult.
  • Es wird überprüft, ob die Eigenschaftswerte korrekt zugewiesen werden.Checking that property value assignments are made correctly.

Diese Gruppe von Tests simulieren häufig die Methoden der DAL, um erwartete Daten für den Schritt „Act“ (Aktion ausführen) zu erzeugen, in dem eine Seitenmodellmethode ausgeführt wird.This group of tests often mock the methods of the DAL to produce expected data for the Act step where a page model method is executed. Beispielsweise wird die GetMessagesAsync des AppDbContext-Methode simuliert, um eine Ausgabe zu erzeugen.For example, the GetMessagesAsync method of the AppDbContext is mocked to produce output. Wenn diese Methode von einer Seitenmodellmethode ausgeführt wird, gibt die Simulation das Ergebnis zurück.When a page model method executes this method, the mock returns the result. Die Daten stammen nicht aus der Datenbank.The data doesn't come from the database. Dadurch werden vorhersagbare, zuverlässige Testbedingungen für die Verwendung der DAL in den Seitenmodelltests erstellt.This creates predictable, reliable test conditions for using the DAL in the page model tests.

Der Test OnGetAsync_PopulatesThePageModel_WithAListOfMessages zeigt, wie die GetMessagesAsync-Methode für das Seitenmodell simuliert wird:The OnGetAsync_PopulatesThePageModel_WithAListOfMessages test shows how the GetMessagesAsync method is mocked for the page model:

var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
    db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);

Wenn die OnGetAsync-Methode im Schritt „Act“ (Aktion ausführen) ausgeführt wird, wird die GetMessagesAsync-Methode des Seitenmodells aufgerufen.When the OnGetAsync method is executed in the Act step, it calls the page model's GetMessagesAsync method.

Schritt „Act“ (Aktion ausführen) im Komponententest (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):Unit test Act step (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

OnGetAsync-Methode des IndexPage-Seitenmodells (src/RazorPagesTestSample/Pages/Index.cshtml.cs):IndexPage page model's OnGetAsync method (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Die GetMessagesAsync-Methode in der DAL gibt kein Ergebnis für diesen Methodenaufruf zurück.The GetMessagesAsync method in the DAL doesn't return the result for this method call. Die simulierte Version der Methode gibt das Ergebnis zurück.The mocked version of the method returns the result.

Im Schritt Assert werden die tatsächlichen Nachrichten (actualMessages) aus der Messages-Eigenschaft des Seitenmodells zugewiesen.In the Assert step, the actual messages (actualMessages) are assigned from the Messages property of the page model. Außerdem wird eine Typüberprüfung ausgeführt, wenn die Nachrichten zugewiesen werden.A type check is also performed when the messages are assigned. Die erwarteten und tatsächlichen Nachrichten werden anhand ihrer Text-Eigenschaften miteinander verglichen.The expected and actual messages are compared by their Text properties. Der Test bestätigt, dass die beiden List<Message>-Instanzen die gleichen Nachrichten enthalten.The test asserts that the two List<Message> instances contain the same messages.

// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
    expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
    actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Bei anderen Tests in dieser Gruppe werden Seitenmodellobjekte erstellt, die den DefaultHttpContext, das ModelStateDictionary, einen ActionContext zum Einrichten des PageContext, eines ViewDataDictionary und eines PageContext enthalten.Other tests in this group create page model objects that include the DefaultHttpContext, the ModelStateDictionary, an ActionContext to establish the PageContext, a ViewDataDictionary, and a PageContext. Diese sind beim Durchführen von Tests nützlich.These are useful in conducting tests. Beispielsweise produziert die Nachrichten-App einen ModelState-Fehler mit AddModelError, um zu überprüfen, ob beim Ausführen von OnPostAddMessageAsync ein gültiges PageResult zurückgegeben wird:For example, the message app establishes a ModelState error with AddModelError to check that a valid PageResult is returned when OnPostAddMessageAsync is executed:

[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
    // Arrange
    var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("InMemoryDb");
    var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
    var expectedMessages = AppDbContext.GetSeedingMessages();
    mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
    var httpContext = new DefaultHttpContext();
    var modelState = new ModelStateDictionary();
    var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
    var modelMetadataProvider = new EmptyModelMetadataProvider();
    var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
    var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
    var pageContext = new PageContext(actionContext)
    {
        ViewData = viewData
    };
    var pageModel = new IndexModel(mockAppDbContext.Object)
    {
        PageContext = pageContext,
        TempData = tempData,
        Url = new UrlHelper(actionContext)
    };
    pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");

    // Act
    var result = await pageModel.OnPostAddMessageAsync();

    // Assert
    Assert.IsType<PageResult>(result);
}

Zusätzliche RessourcenAdditional resources

ASP.NET Core unterstützt Komponententests von Razor Pages-Apps.ASP.NET Core supports unit tests of Razor Pages apps. Tests der Datenzugriffsebene (Data Access Layer, DAL) und Seitenmodelle helfen dabei, Folgendes sicherzustellen:Tests of the data access layer (DAL) and page models help ensure:

  • Teile einer Razor Pages-App funktionieren während der App-Erstellung unabhängig und zusammen als Einheit.Parts of a Razor Pages app work independently and together as a unit during app construction.
  • Klassen und Methoden weisen eingeschränkte Zuständigkeitsbereiche auf.Classes and methods have limited scopes of responsibility.
  • Es gibt zusätzliche Dokumentation zum Verhalten der App.Additional documentation exists on how the app should behave.
  • Regressionen, bei denen es sich um Fehler bei Aktualisierungen des Codes handelt, werden während der automatisierten Erstellung und Bereitstellung gefunden.Regressions, which are errors brought about by updates to the code, are found during automated building and deployment.

In diesem Thema wird davon ausgegangen, dass Sie über grundlegende Kenntnisse über Razor Pages-Apps und -Komponententests verfügen.This topic assumes that you have a basic understanding of Razor Pages apps and unit tests. Wenn Sie mit Razor Pages-Apps oder Testkonzepten nicht vertraut sind, finden Sie weitere Informationen in den folgenden Themen:If you're unfamiliar with Razor Pages apps or test concepts, see the following topics:

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

Das Beispielprojekt besteht aus zwei Apps:The sample project is composed of two apps:

AppApp ProjektordnerProject folder BeschreibungDescription
Nachrichten-AppMessage app src/RazorPagesTestSamplesrc/RazorPagesTestSample Ermöglicht es dem Benutzer, eine Nachricht hinzuzufügen, eine Nachricht zu löschen, alle Nachrichten zu löschen und Nachrichten zu analysieren (durch Ermitteln der durchschnittlichen Anzahl von Wörtern pro Nachricht).Allows a user to add a message, delete one message, delete all messages, and analyze messages (find the average number of words per message).
Testen der AppTest app tests/RazorPagesTestSample.Teststests/RazorPagesTestSample.Tests Wird verwendet, um Komponententests für die DAL und das Indexseitenmodell der Nachrichten-App durchzusetzen.Used to unit test the DAL and Index page model of the message app.

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

dotnet test

Organisation der Nachrichten-AppMessage app organization

Bei der Nachrichten-App handelt es sich um ein Razor Pages-Nachrichtensystem mit folgenden Merkmalen:The message app 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 der Sie das Hinzufügen, Löschen und Analysieren von Nachrichten (durch Ermitteln der durchschnittlichen 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 (find the average number of 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 DAL in ihrer Datenbankkontext-Klasse, AppDbContext (Data/AppDbContext.cs).The app contains a DAL in its database context class, AppDbContext (Data/AppDbContext.cs). Die DAL-Methoden sind als virtual gekennzeichnet, sodass die Methoden für die Verwendung in den Tests simuliert werden können.The DAL methods are marked virtual, which allows mocking the methods for use in the tests.
  • 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. Diese per Seeding hinzugefügten Nachrichten werden auch in Tests verwendet.These seeded messages are also used in tests.

†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 Beispiel-App nicht das Repositorymuster verwendet und kein effektives Beispiel für das Arbeitseinheitsmuster ist, unterstützt Razor Pages diese Entwicklungsmuster.Although the sample 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 Testen von Controllerlogik in ASP.NET Core (im Beispiel wird das Repositorymuster implementiert).For more information, see Designing the infrastructure persistence layer and Testen von Controllerlogik in ASP.NET Core (the sample implements the repository pattern).

Organisation der Test-AppTest app organization

Bei der Test-App handelt es sich um eine Konsolen-App innerhalb des Ordners tests/RazorPagesTestSample.Tests.The test app is a console app inside the tests/RazorPagesTestSample.Tests folder.

Test-App-OrdnerTest app folder BeschreibungDescription
UnitTestsUnitTests
  • DataAccessLayerTest.cs enthält die Komponententests für die DAL.DataAccessLayerTest.cs contains the unit tests for the DAL.
  • IndexPageTests.cs enthält die Komponententests für das Indexseitenmodell.IndexPageTests.cs contains the unit tests for the Index page model.
UtilitiesUtilities Enthält die TestDbContextOptions-Methode, mit der neue Datenbankkontext-Optionen für jeden DAL-Komponententest erstellt werden, sodass die Datenbank für jeden Test auf ihren Ausgangszustand zurückgesetzt wird.Contains the TestDbContextOptions method used to create new database context options for each DAL unit test so that the database is reset to its baseline condition for each test.

Das Testframework ist xUnit.The test framework is xUnit. Das Framework für die Objektsimulation ist Moq.The object mocking framework is Moq.

Komponententests der Datenzugriffsebene (Data Access Layer, DAL)Unit tests of the data access layer (DAL)

Die Nachrichten-App verfügt über eine Datenzugriffsebene mit vier Methoden, die in der AppDbContext-Klasse (src/RazorPagesTestSample/Data/AppDbContext.cs) enthalten sind.The message app has a DAL with four methods contained in the AppDbContext class (src/RazorPagesTestSample/Data/AppDbContext.cs). Jede Methode verfügt über einen oder zwei Komponententests in der Test-App.Each method has one or two unit tests in the test app.

DAL-MethodeDAL method FunktionFunction
GetMessagesAsync Ruft eine nach der Eigenschaft Text sortierte List<Message> aus der Datenbank ab.Obtains a List<Message> from the database sorted by the Text property.
AddMessageAsync Fügt der Datenbank eine Message hinzu.Adds a Message to the database.
DeleteAllMessagesAsync Löscht alle Message-Einträge aus der Datenbank.Deletes all Message entries from the database.
DeleteMessageAsync Löscht eine einzelne, nach Id sortierte Message aus der Datenbank.Deletes a single Message from the database by Id.

Komponententests der DAL erfordern DbContextOptions, wenn für jeden Test ein neuer AppDbContext erstellt wird.Unit tests of the DAL require DbContextOptions when creating a new AppDbContext for each test. Ein Ansatz zum Erstellen der DbContextOptions für jeden Test ist die Verwendung eines DbContextOptionsBuilder:One approach to creating the DbContextOptions for each test is to use a DbContextOptionsBuilder:

var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
    .UseInMemoryDatabase("InMemoryDb");

using (var db = new AppDbContext(optionsBuilder.Options))
{
    // Use the db here in the unit test.
}

Das Problem bei diesem Ansatz besteht darin, dass jeder Test die Datenbank in dem Zustand empfängt, in dem sie sich im vorherigen Test zum Schluss befand.The problem with this approach is that each test receives the database in whatever state the previous test left it. Dies kann problematisch sein, wenn versucht wird, atomische Komponententests zu schreiben, die sich nicht einander beeinträchtigen.This can be problematic when trying to write atomic unit tests that don't interfere with each other. Um zu erzwingen, dass der AppDbContext für jeden Test einen neuen Datenbankkontext verwendet, stellen Sie eine DbContextOptions-Instanz bereit, die auf einem neuen Dienstanbieter basiert.To force the AppDbContext to use a new database context for each test, supply a DbContextOptions instance that's based on a new service provider. Die Test-App zeigt, wie dies mit der Utilities-Klassenmethode TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs) möglich ist:The test app shows how to do this using its Utilities class method TestDbContextOptions (tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs):

public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
    // Create a new service provider to create a new in-memory database.
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkInMemoryDatabase()
        .BuildServiceProvider();

    // Create a new options instance using an in-memory database and 
    // IServiceProvider that the context should resolve all of its 
    // services from.
    var builder = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("InMemoryDb")
        .UseInternalServiceProvider(serviceProvider);

    return builder.Options;
}

Wenn Sie die DbContextOptions in den DAL-Komponententests verwenden, kann jeder Test atomisch mit einer neuen Datenbankinstanz ausgeführt werden:Using the DbContextOptions in the DAL unit tests allows each test to run atomically with a fresh database instance:

using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
    // Use the db here in the unit test.
}

Jede Testmethode in der DataAccessLayerTest-Klasse (UnitTests/DataAccessLayerTest.cs) folgt einem ähnlichen „Arrange-Act-Assert“-Muster:Each test method in the DataAccessLayerTest class (UnitTests/DataAccessLayerTest.cs) follows a similar Arrange-Act-Assert pattern:

  1. „Arrange“ (Anordnen) Die Datenbank ist für den Test konfiguriert, und/oder das erwartete Ergebnis ist definiert.Arrange: The database is configured for the test and/or the expected outcome is defined.
  2. „Act“ (Aktion ausführen): Der Test wird ausgeführt.Act: The test is executed.
  3. Assert (Bestätigen): Es werden Assertionen erstellt, um zu bestimmen, ob das Testergebnis ein Erfolg ist.Assert: Assertions are made to determine if the test result is a success.

Beispielsweise ist die Methode DeleteMessageAsync für das Entfernen einer einzelnen Nachricht verantwortlich, die durch Id (src/RazorPagesTestSample/Data/AppDbContext.cs) identifiziert wird:For example, the DeleteMessageAsync method is responsible for removing a single message identified by its Id (src/RazorPagesTestSample/Data/AppDbContext.cs):

public async virtual Task DeleteMessageAsync(int id)
{
    var message = await Messages.FindAsync(id);

    if (message != null)
    {
        Messages.Remove(message);
        await SaveChangesAsync();
    }
}

Für diese Methode gibt es zwei Tests.There are two tests for this method. Ein Test prüft, ob die Methode eine Nachricht löscht, wenn die Nachricht in der Datenbank vorhanden ist.One test checks that the method deletes a message when the message is present in the database. Die andere Methode testet, ob die Datenbank nicht geändert wird, wenn die Nachrichten-Id zum Löschen nicht vorhanden ist.The other method tests that the database doesn't change if the message Id for deletion doesn't exist. Die DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound-Methode wird im folgenden Beispiel dargestellt:The DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound method is shown below:

[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
    using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
    {
        // Arrange
        var seedMessages = AppDbContext.GetSeedingMessages();
        await db.AddRangeAsync(seedMessages);
        await db.SaveChangesAsync();
        var recId = 1;
        var expectedMessages = 
            seedMessages.Where(message => message.Id != recId).ToList();

        // Act
        await db.DeleteMessageAsync(recId);

        // Assert
        var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
        Assert.Equal(
            expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
            actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
    }
}

Zuerst führt die Methode den Schritt „Arrange“ (Anordnen) aus, bei dem die Vorbereitung für den Schritt „Act“ (Aktion ausführen) stattfindet.First, the method performs the Arrange step, where preparation for the Act step takes place. Die Seeding-Nachrichten werden abgerufen und in seedMessages gespeichert.The seeding messages are obtained and held in seedMessages. Die Seeding-Nachrichten werden in der Datenbank gespeichert.The seeding messages are saved into the database. Die Nachricht mit einer Id 1 wird zum Löschen festgelegt.The message with an Id of 1 is set for deletion. Wenn die DeleteMessageAsync-Methode ausgeführt wird, sollten die erwarteten Nachrichten alle Nachrichten umfassen, mit Ausnahme derjenigen, die eine Id 1 aufweisen.When the DeleteMessageAsync method is executed, the expected messages should have all of the messages except for the one with an Id of 1. Die Variable expectedMessages stellt dieses erwartete Ergebnis dar.The expectedMessages variable represents this expected outcome.

// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages = 
    seedMessages.Where(message => message.Id != recId).ToList();

Die Methode verhält sich wie folgt: Die DeleteMessageAsync-Methode wird ausgeführt, indem die recId 1 übergeben wird:The method acts: The DeleteMessageAsync method is executed passing in the recId of 1:

// Act
await db.DeleteMessageAsync(recId);

Abschließend ruft die Methode die Messages aus dem Kontext ab und vergleicht sie mit den expectedMessages mit der Behauptung, dass die beiden identisch sind:Finally, the method obtains the Messages from the context and compares it to the expectedMessages asserting that the two are equal:

// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
    expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
    actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Um vergleichen zu können, ob die beiden List<Message> identisch sind, werden folgende Aktionen ausgeführt:In order to compare that the two List<Message> are the same:

  • Die Nachrichten werden nach Id sortiert.The messages are ordered by Id.
  • Nachrichtenpaare werden anhand der Text-Eigenschaft verglichen.Message pairs are compared on the Text property.

DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound, eine ähnliche Testmethode, überprüft das Ergebnis des Versuchs, eine Meldung zu löschen, die nicht vorhanden ist.A similar test method, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound checks the result of attempting to delete a message that doesn't exist. In diesem Fall sollten die erwarteten Nachrichten in der Datenbank mit den tatsächlichen Nachrichten übereinstimmen, nachdem die DeleteMessageAsync-Methode ausgeführt wurde.In this case, the expected messages in the database should be equal to the actual messages after the DeleteMessageAsync method is executed. Der Inhalt der Datenbank sollte nicht geändert werden:There should be no change to the database's content:

[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
    using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
    {
        // Arrange
        var expectedMessages = AppDbContext.GetSeedingMessages();
        await db.AddRangeAsync(expectedMessages);
        await db.SaveChangesAsync();
        var recId = 4;

        // Act
        await db.DeleteMessageAsync(recId);

        // Assert
        var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
        Assert.Equal(
            expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
            actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
    }
}

Komponententests der SeitenmodellmethodenUnit tests of the page model methods

Weitere Komponententests sind für Tests von Seitenmodellmethoden verantwortlich.Another set of unit tests is responsible for tests of page model methods. In der Nachrichten-App befinden sich die Indexseitenmodelle in der IndexModel-Klasse in src/RazorPagesTestSample/Pages/Index.cshtml.cs.In the message app, the Index page models are found in the IndexModel class in src/RazorPagesTestSample/Pages/Index.cshtml.cs.

SeitenmodellmethodePage model method FunktionFunction
OnGetAsync Ruft die Nachrichten aus der DAL für die Benutzeroberfläche mithilfe der GetMessagesAsync-Methode ab.Obtains the messages from the DAL for the UI using the GetMessagesAsync method.
OnPostAddMessageAsync Wenn der ModelState gültig ist, wird AddMessageAsync aufgerufen, um der Datenbank eine Nachricht hinzuzufügen.If the ModelState is valid, calls AddMessageAsync to add a message to the database.
OnPostDeleteAllMessagesAsync Ruft DeleteAllMessagesAsync auf, um alle Nachrichten in der Datenbank zu löschen.Calls DeleteAllMessagesAsync to delete all of the messages in the database.
OnPostDeleteMessageAsync Führt DeleteMessageAsync aus, um eine Meldung mit der angegebenen Id zu löschen.Executes DeleteMessageAsync to delete a message with the Id specified.
OnPostAnalyzeMessagesAsync Wenn sich eine oder mehrere Nachrichten in der Datenbank befinden, wird die durchschnittliche Anzahl von Wörtern pro Nachricht berechnet.If one or more messages are in the database, calculates the average number of words per message.

Die Seitenmodellmethoden werden mithilfe von sieben Tests in der IndexPageTests-Klasse (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs) getestet.The page model methods are tested using seven tests in the IndexPageTests class (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Bei den Tests wird das bekannte „Arrange-Act-Assert“-Muster verwendet.The tests use the familiar Arrange-Assert-Act pattern. Diese Tests konzentrieren sich auf folgende Punkte:These tests focus on:

  • Es wird ermittelt, ob die Methoden das richtige Verhalten aufweisen, wenn der ModelState ungültig ist.Determining if the methods follow the correct behavior when the ModelState is invalid.
  • Es wird überprüft, ob nach dem Bestätigen der Methode das richtige IActionResult erzeugt wird.Confirming the methods produce the correct IActionResult.
  • Es wird überprüft, ob die Eigenschaftswerte korrekt zugewiesen werden.Checking that property value assignments are made correctly.

Diese Gruppe von Tests simulieren häufig die Methoden der DAL, um erwartete Daten für den Schritt „Act“ (Aktion ausführen) zu erzeugen, in dem eine Seitenmodellmethode ausgeführt wird.This group of tests often mock the methods of the DAL to produce expected data for the Act step where a page model method is executed. Beispielsweise wird die GetMessagesAsync des AppDbContext-Methode simuliert, um eine Ausgabe zu erzeugen.For example, the GetMessagesAsync method of the AppDbContext is mocked to produce output. Wenn diese Methode von einer Seitenmodellmethode ausgeführt wird, gibt die Simulation das Ergebnis zurück.When a page model method executes this method, the mock returns the result. Die Daten stammen nicht aus der Datenbank.The data doesn't come from the database. Dadurch werden vorhersagbare, zuverlässige Testbedingungen für die Verwendung der DAL in den Seitenmodelltests erstellt.This creates predictable, reliable test conditions for using the DAL in the page model tests.

Der Test OnGetAsync_PopulatesThePageModel_WithAListOfMessages zeigt, wie die GetMessagesAsync-Methode für das Seitenmodell simuliert wird:The OnGetAsync_PopulatesThePageModel_WithAListOfMessages test shows how the GetMessagesAsync method is mocked for the page model:

var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
    db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);

Wenn die OnGetAsync-Methode im Schritt „Act“ (Aktion ausführen) ausgeführt wird, wird die GetMessagesAsync-Methode des Seitenmodells aufgerufen.When the OnGetAsync method is executed in the Act step, it calls the page model's GetMessagesAsync method.

Schritt „Act“ (Aktion ausführen) im Komponententest (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):Unit test Act step (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

OnGetAsync-Methode des IndexPage-Seitenmodells (src/RazorPagesTestSample/Pages/Index.cshtml.cs):IndexPage page model's OnGetAsync method (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Die GetMessagesAsync-Methode in der DAL gibt kein Ergebnis für diesen Methodenaufruf zurück.The GetMessagesAsync method in the DAL doesn't return the result for this method call. Die simulierte Version der Methode gibt das Ergebnis zurück.The mocked version of the method returns the result.

Im Schritt Assert werden die tatsächlichen Nachrichten (actualMessages) aus der Messages-Eigenschaft des Seitenmodells zugewiesen.In the Assert step, the actual messages (actualMessages) are assigned from the Messages property of the page model. Außerdem wird eine Typüberprüfung ausgeführt, wenn die Nachrichten zugewiesen werden.A type check is also performed when the messages are assigned. Die erwarteten und tatsächlichen Nachrichten werden anhand ihrer Text-Eigenschaften miteinander verglichen.The expected and actual messages are compared by their Text properties. Der Test bestätigt, dass die beiden List<Message>-Instanzen die gleichen Nachrichten enthalten.The test asserts that the two List<Message> instances contain the same messages.

// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
    expectedMessages.OrderBy(m => m.Id).Select(m => m.Text), 
    actualMessages.OrderBy(m => m.Id).Select(m => m.Text));

Bei anderen Tests in dieser Gruppe werden Seitenmodellobjekte erstellt, die den DefaultHttpContext, das ModelStateDictionary, einen ActionContext zum Einrichten des PageContext, eines ViewDataDictionary und eines PageContext enthalten.Other tests in this group create page model objects that include the DefaultHttpContext, the ModelStateDictionary, an ActionContext to establish the PageContext, a ViewDataDictionary, and a PageContext. Diese sind beim Durchführen von Tests nützlich.These are useful in conducting tests. Beispielsweise produziert die Nachrichten-App einen ModelState-Fehler mit AddModelError, um zu überprüfen, ob beim Ausführen von OnPostAddMessageAsync ein gültiges PageResult zurückgegeben wird:For example, the message app establishes a ModelState error with AddModelError to check that a valid PageResult is returned when OnPostAddMessageAsync is executed:

[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
    // Arrange
    var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase("InMemoryDb");
    var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
    var expectedMessages = AppDbContext.GetSeedingMessages();
    mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
    var httpContext = new DefaultHttpContext();
    var modelState = new ModelStateDictionary();
    var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
    var modelMetadataProvider = new EmptyModelMetadataProvider();
    var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
    var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
    var pageContext = new PageContext(actionContext)
    {
        ViewData = viewData
    };
    var pageModel = new IndexModel(mockAppDbContext.Object)
    {
        PageContext = pageContext,
        TempData = tempData,
        Url = new UrlHelper(actionContext)
    };
    pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");

    // Act
    var result = await pageModel.OnPostAddMessageAsync();

    // Assert
    Assert.IsType<PageResult>(result);
}

Zusätzliche RessourcenAdditional resources