Dela via


Razor Enhetstester för sidor i ASP.NET Core

ASP.NET Core stöder enhetstester av Razor Pages-appar. Tester av dataåtkomstskiktet (DAL) och sidmodeller hjälper till att säkerställa:

  • Delar av en Razor Pages-app fungerar oberoende och tillsammans som en enhet under appkonstruktionen.
  • Klasser och metoder har begränsade ansvarsområden.
  • Det finns ytterligare dokumentation om hur appen ska bete sig.
  • Regressioner, som är fel som uppstår vid uppdateringar av koden, hittas under automatisk skapande och distribution.

Det här avsnittet förutsätter att du har en grundläggande förståelse för Razor Pages-appar och enhetstester. Om du inte känner till Razor Pages-appar eller testbegrepp kan du läsa följande avsnitt:

Visa eller ladda ned exempelkod (hur du laddar ned)

Exempelprojektet består av två appar:

App Projektmapp Description
Meddelandeapp src/RazorPagesTestSample Tillåter att en användare lägger till ett meddelande, tar bort ett meddelande, tar bort alla meddelanden och analyserar meddelanden (hitta det genomsnittliga antalet ord per meddelande).
Testapp tests/RazorPagesTestSample.Tests Används för att enhetstesta dal- och indexsidans modell för meddelandeappen.

Testerna kan köras med hjälp av de inbyggda testfunktionerna i en IDE, till exempel Visual Studio. Om du använder Visual Studio Code eller kommandoraden kör du följande kommando i en kommandotolk i mappen tests/RazorPagesTestSample.Tests :

dotnet test

Meddelandeappsorganisation

Meddelandeappen är ett Razor pages-meddelandesystem med följande egenskaper:

  • Sidan Index i appen (Pages/Index.cshtml och Pages/Index.cshtml.cs) innehåller ett användargränssnitt och sidmodellmetoder för att styra tillägg, borttagning och analys av meddelanden (hitta det genomsnittliga antalet ord per meddelande).
  • Ett meddelande beskrivs av klassen Message (Data/Message.cs) med två egenskaper: Id (nyckel) och Text (meddelande). Egenskapen Text krävs och är begränsad till 200 tecken.
  • Meddelanden lagras med hjälp av Entity Frameworks minnesinterna databas†.
  • Appen innehåller en DAL i sin databaskontextklass, AppDbContext (Data/AppDbContext.cs). DAL-metoderna är märkta virtual, vilket gör det möjligt att simulera metoderna för användning i testerna.
  • Om databasen är tom vid appstart initieras meddelandearkivet med tre meddelanden. Dessa seeded-meddelanden används också i tester.

† EF-ämnet, Test with InMemory, förklarar hur du använder en minnesintern databas för tester med MSTest. Det här avsnittet använder testramverket xUnit. Testbegrepp och testimplementeringar i olika testramverk är liknande men inte identiska.

Även om exempelappen inte använder lagringsplatsens mönster och inte är ett effektivt exempel på UoW-mönstret (Unit of Work) stöder Pages dessa utvecklingsmönster. Razor Mer information finns i Designa infrastrukturens beständighetslager och teststyrenhetslogik i ASP.NET Core (exemplet implementerar lagringsplatsens mönster).

Testapporganisation

Testappen är en konsolapp i mappen tests/RazorPagesTestSample.Tests .

Testa appmappen Description
UnitTests
  • DataAccessLayerTest.cs innehåller enhetstesterna för DAL.
  • IndexPageTests.cs innehåller enhetstesterna för indexsidans modell.
Utilities Innehåller den TestDbContextOptions metod som används för att skapa nya databaskontextalternativ för varje DAL-enhetstest så att databasen återställs till baslinjevillkoret för varje test.

Testramverket är xUnit. Ramverket för att håna objektet är Moq.

Enhetstester av dataåtkomstskiktet (DAL)

Meddelandeappen har en DAL med fyra metoder i AppDbContext klassen (src/RazorPagesTestSample/Data/AppDbContext.cs). Varje metod har en eller två enhetstester i testappen.

DAL-metod Function
GetMessagesAsync Hämtar en List<Message> från databasen som sorteras efter egenskapen Text .
AddMessageAsync Lägger till en Message i databasen.
DeleteAllMessagesAsync Tar bort alla Message poster från databasen.
DeleteMessageAsync Tar bort en enskild Message från databasen med Id.

Enhetstester av DAL kräver DbContextOptions vid skapandet av en ny AppDbContext för varje test. En metod för att skapa DbContextOptions för varje test är att använda en DbContextOptionsBuilder:

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

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

Problemet med den här metoden är att varje test tar emot databasen i det tillstånd som föregående test lämnade den. Detta kan vara problematiskt när du försöker skriva atomiska enhetstester som inte stör varandra. Om du vill tvinga AppDbContext att använda en ny databaskontext för varje test anger du en DbContextOptions instans som baseras på en ny tjänstleverantör. Testappen visar hur du gör detta med hjälp av dess Utilities klassmetod 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;
}

Med hjälp av DbContextOptions i enhetstesterna för DAL kan varje test köras atomiskt med en ny databasinstans.

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

Varje testmetod i DataAccessLayerTest klassen (UnitTests/DataAccessLayerTest.cs) följer ett liknande Arrange-Act-Assert-mönster:

  1. Ordna: Databasen är konfigurerad för testet och/eller det förväntade resultatet har definierats.
  2. Agera: Testet körs.
  3. Påstående: Påståenden görs för att avgöra om testresultatet är framgångsrikt.

Metoden ansvarar till exempel DeleteMessageAsync för att ta bort ett enda meddelande som identifieras av dess 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();
    }
}

Det finns två tester för den här metoden. Ett test kontrollerar att metoden tar bort ett meddelande när meddelandet finns i databasen. Den andra metoden testar att databasen inte ändras om meddelandet Id för borttagning inte finns. Metoden DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound visas nedan:

[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));
    }
}

Först utför metoden steget Ordna, där förberedelserna för act-steget sker. Seeding-meddelandena hämtas och lagras i seedMessages. Seeding-meddelandena sparas i databasen. Meddelandet med ett Id av 1 har angetts för borttagning. DeleteMessageAsync När metoden körs ska de förväntade meddelandena ha alla meddelanden förutom den med en Id av 1. Variabeln expectedMessages representerar det förväntade resultatet.

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

Metoden agerar: Metoden DeleteMessageAsync körs och skickar i recId :1

// Act
await db.DeleteMessageAsync(recId);

Slutligen hämtar metoden Messages från kontexten och jämför den med expectedMessages och försäkrar att de två är lika:

// 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));

För att jämföra att de två List<Message> är samma:

  • Meddelandena sorteras efter Id.
  • Meddelandepar jämförs på egenskapen Text .

En liknande testmetod DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound kontrollerar resultatet av att försöka ta bort ett meddelande som inte finns. I det här fallet ska de förväntade meddelandena i databasen vara lika med de faktiska meddelandena efter DeleteMessageAsync att metoden har körts. Databasens innehåll bör inte ändras:

[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));
    }
}

Enhetstester av sidmodellmetoderna

En annan uppsättning enhetstester ansvarar för tester av sidmodellmetoder. I meddelandeappen finns indexsidans modeller i IndexModel klassen i src/RazorPagesTestSample/Pages/Index.cshtml.cs.

Sidmodellmetod Function
OnGetAsync Hämtar meddelandena från DAL för användargränssnittet med hjälp av GetMessagesAsync metoden .
OnPostAddMessageAsync Om ModelState är giltigt anropar du AddMessageAsync för att lägga till ett meddelande i databasen.
OnPostDeleteAllMessagesAsync Anropar DeleteAllMessagesAsync för att ta bort alla meddelanden i databasen.
OnPostDeleteMessageAsync Kör DeleteMessageAsync för att ta bort ett meddelande med det angivna Id.
OnPostAnalyzeMessagesAsync Om ett eller flera meddelanden finns i databasen beräknas det genomsnittliga antalet ord per meddelande.

Sidmodellmetoderna testas med hjälp av sju tester i IndexPageTests klassen (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Testerna använder det välbekanta mönstret Ordna-Agera-Verifiera. Dessa tester fokuserar på:

  • Avgöra om metoderna följer rätt beteende när ModelState är ogiltigt.
  • Att bekräfta att metoderna ger rätt IActionResult.
  • Kontrollera att egenskapsvärdetilldelningar görs korrekt.

Den här gruppen med tester hånar ofta metoderna för DAL för att producera förväntade data för act-steget där en sidmodellmetod körs. Metoden GetMessagesAsync för AppDbContext är till exempel mockad för att generera utdata. När en sidmodellsmetod anropar denna metod returnerar mockobjektet resultatet. Data kommer inte från databasen. Detta skapar förutsägbara och tillförlitliga testvillkor för att använda DAL i sidmodelltesterna.

Testet OnGetAsync_PopulatesThePageModel_WithAListOfMessages visar hur GetMessagesAsync metod mockas för sidmodellen:

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);

OnGetAsync När metoden körs i steget Agera anropas sidmodellens GetMessagesAsync metod.

Akt-steg i enhetstest (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

IndexPage sidmodellens OnGetAsync metod (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Metoden GetMessagesAsync i DAL returnerar inte resultatet för det här metodanropet. Den simulerade versionen av metoden returnerar resultatet.

I Assert-steget tilldelas de faktiska meddelandena (actualMessages) från sidmodellens Messages-egenskap. En typkontroll utförs också när meddelandena tilldelas. De förväntade och faktiska meddelandena jämförs med deras Text egenskaper. Testet hävdar att de två List<Message> instanserna innehåller samma meddelanden.

// 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));

Andra tester i den här gruppen skapar sidmodellobjekt som innehåller DefaultHttpContext, ModelStateDictionary, en ActionContext för att upprätta PageContext, en ViewDataDictionaryoch en PageContext. Dessa är användbara för att utföra tester. Meddelandeappen upprättar till exempel ett ModelState fel med AddModelError för att kontrollera att ett giltigt PageResult returneras när OnPostAddMessageAsync körs:

[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);
}

Ytterligare resurser

ASP.NET Core stöder enhetstester av Razor Pages-appar. Tester av dataåtkomstskiktet (DAL) och sidmodeller hjälper till att säkerställa:

  • Delar av en Razor Pages-app fungerar oberoende och tillsammans som en enhet under appkonstruktionen.
  • Klasser och metoder har begränsade ansvarsområden.
  • Det finns ytterligare dokumentation om hur appen ska bete sig.
  • Regressioner, som är fel som uppstår vid uppdateringar av koden, hittas under automatisk skapande och distribution.

Det här avsnittet förutsätter att du har en grundläggande förståelse för Razor Pages-appar och enhetstester. Om du inte känner till Razor Pages-appar eller testbegrepp kan du läsa följande avsnitt:

Visa eller ladda ned exempelkod (hur du laddar ned)

Exempelprojektet består av två appar:

App Projektmapp Description
Meddelandeapp src/RazorPagesTestSample Tillåter att en användare lägger till ett meddelande, tar bort ett meddelande, tar bort alla meddelanden och analyserar meddelanden (hitta det genomsnittliga antalet ord per meddelande).
Testapp tests/RazorPagesTestSample.Tests Används för att enhetstesta dal- och indexsidans modell för meddelandeappen.

Testerna kan köras med hjälp av de inbyggda testfunktionerna i en IDE, till exempel Visual Studio. Om du använder Visual Studio Code eller kommandoraden kör du följande kommando i en kommandotolk i mappen tests/RazorPagesTestSample.Tests :

dotnet test

Meddelandeappsorganisation

Meddelandeappen är ett Razor pages-meddelandesystem med följande egenskaper:

  • Sidan Index i appen (Pages/Index.cshtml och Pages/Index.cshtml.cs) innehåller ett användargränssnitt och sidmodellmetoder för att styra tillägg, borttagning och analys av meddelanden (hitta det genomsnittliga antalet ord per meddelande).
  • Ett meddelande beskrivs av klassen Message (Data/Message.cs) med två egenskaper: Id (nyckel) och Text (meddelande). Egenskapen Text krävs och är begränsad till 200 tecken.
  • Meddelanden lagras med hjälp av Entity Frameworks minnesinterna databas†.
  • Appen innehåller en DAL i sin databaskontextklass, AppDbContext (Data/AppDbContext.cs). DAL-metoderna är märkta virtual, vilket gör det möjligt att simulera metoderna för användning i testerna.
  • Om databasen är tom vid appstart initieras meddelandearkivet med tre meddelanden. Dessa seeded-meddelanden används också i tester.

† EF-ämnet, Test with InMemory, förklarar hur du använder en minnesintern databas för tester med MSTest. Det här avsnittet använder testramverket xUnit. Testbegrepp och testimplementeringar i olika testramverk är liknande men inte identiska.

Även om exempelappen inte använder lagringsplatsens mönster och inte är ett effektivt exempel på UoW-mönstret (Unit of Work) stöder Pages dessa utvecklingsmönster. Razor Mer information finns i Designa infrastrukturens beständighetslager och teststyrenhetslogik i ASP.NET Core (exemplet implementerar lagringsplatsens mönster).

Testapporganisation

Testappen är en konsolapp i mappen tests/RazorPagesTestSample.Tests .

Testa appmappen Description
UnitTests
  • DataAccessLayerTest.cs innehåller enhetstesterna för DAL.
  • IndexPageTests.cs innehåller enhetstesterna för indexsidans modell.
Utilities Innehåller den TestDbContextOptions metod som används för att skapa nya databaskontextalternativ för varje DAL-enhetstest så att databasen återställs till baslinjevillkoret för varje test.

Testramverket är xUnit. Ramverket för att håna objektet är Moq.

Enhetstester av dataåtkomstskiktet (DAL)

Meddelandeappen har en DAL med fyra metoder i AppDbContext klassen (src/RazorPagesTestSample/Data/AppDbContext.cs). Varje metod har en eller två enhetstester i testappen.

DAL-metod Function
GetMessagesAsync Hämtar en List<Message> från databasen som sorteras efter egenskapen Text .
AddMessageAsync Lägger till en Message i databasen.
DeleteAllMessagesAsync Tar bort alla Message poster från databasen.
DeleteMessageAsync Tar bort en enskild Message från databasen med Id.

Enhetstester av DAL kräver DbContextOptions vid skapandet av en ny AppDbContext för varje test. En metod för att skapa DbContextOptions för varje test är att använda en DbContextOptionsBuilder:

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

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

Problemet med den här metoden är att varje test tar emot databasen i det tillstånd som föregående test lämnade den. Detta kan vara problematiskt när du försöker skriva atomiska enhetstester som inte stör varandra. Om du vill tvinga AppDbContext att använda en ny databaskontext för varje test anger du en DbContextOptions instans som baseras på en ny tjänstleverantör. Testappen visar hur du gör detta med hjälp av dess Utilities klassmetod 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;
}

Med hjälp av DbContextOptions i enhetstesterna för DAL kan varje test köras atomiskt med en ny databasinstans.

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

Varje testmetod i DataAccessLayerTest klassen (UnitTests/DataAccessLayerTest.cs) följer ett liknande Arrange-Act-Assert-mönster:

  1. Ordna: Databasen är konfigurerad för testet och/eller det förväntade resultatet har definierats.
  2. Agera: Testet körs.
  3. Påstående: Påståenden görs för att avgöra om testresultatet är framgångsrikt.

Metoden ansvarar till exempel DeleteMessageAsync för att ta bort ett enda meddelande som identifieras av dess 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();
    }
}

Det finns två tester för den här metoden. Ett test kontrollerar att metoden tar bort ett meddelande när meddelandet finns i databasen. Den andra metoden testar att databasen inte ändras om meddelandet Id för borttagning inte finns. Metoden DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound visas nedan:

[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));
    }
}

Först utför metoden steget Ordna, där förberedelserna för act-steget sker. Seeding-meddelandena hämtas och lagras i seedMessages. Seeding-meddelandena sparas i databasen. Meddelandet med ett Id av 1 har angetts för borttagning. DeleteMessageAsync När metoden körs ska de förväntade meddelandena ha alla meddelanden förutom den med en Id av 1. Variabeln expectedMessages representerar det förväntade resultatet.

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

Metoden agerar: Metoden DeleteMessageAsync körs och skickar i recId :1

// Act
await db.DeleteMessageAsync(recId);

Slutligen hämtar metoden Messages från kontexten och jämför den med expectedMessages och försäkrar att de två är lika:

// 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));

För att jämföra att de två List<Message> är samma:

  • Meddelandena sorteras efter Id.
  • Meddelandepar jämförs på egenskapen Text .

En liknande testmetod DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound kontrollerar resultatet av att försöka ta bort ett meddelande som inte finns. I det här fallet ska de förväntade meddelandena i databasen vara lika med de faktiska meddelandena efter DeleteMessageAsync att metoden har körts. Databasens innehåll bör inte ändras:

[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));
    }
}

Enhetstester av sidmodellmetoderna

En annan uppsättning enhetstester ansvarar för tester av sidmodellmetoder. I meddelandeappen finns indexsidans modeller i IndexModel klassen i src/RazorPagesTestSample/Pages/Index.cshtml.cs.

Sidmodellmetod Function
OnGetAsync Hämtar meddelandena från DAL för användargränssnittet med hjälp av GetMessagesAsync metoden .
OnPostAddMessageAsync Om ModelState är giltigt anropar du AddMessageAsync för att lägga till ett meddelande i databasen.
OnPostDeleteAllMessagesAsync Anropar DeleteAllMessagesAsync för att ta bort alla meddelanden i databasen.
OnPostDeleteMessageAsync Kör DeleteMessageAsync för att ta bort ett meddelande med det angivna Id.
OnPostAnalyzeMessagesAsync Om ett eller flera meddelanden finns i databasen beräknas det genomsnittliga antalet ord per meddelande.

Sidmodellmetoderna testas med hjälp av sju tester i IndexPageTests klassen (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Testerna använder det välbekanta mönstret OrdnaAct-Assert. Dessa tester fokuserar på:

  • Avgöra om metoderna följer rätt beteende när ModelState är ogiltigt.
  • Att bekräfta att metoderna ger rätt IActionResult.
  • Kontrollera att egenskapsvärdetilldelningar görs korrekt.

Den här gruppen med tester hånar ofta metoderna för DAL för att producera förväntade data för act-steget där en sidmodellmetod körs. Metoden GetMessagesAsync för AppDbContext är till exempel mockad för att generera utdata. När en sidmodellsmetod anropar denna metod returnerar mockobjektet resultatet. Data kommer inte från databasen. Detta skapar förutsägbara och tillförlitliga testvillkor för att använda DAL i sidmodelltesterna.

Testet OnGetAsync_PopulatesThePageModel_WithAListOfMessages visar hur GetMessagesAsync metod mockas för sidmodellen:

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);

OnGetAsync När metoden körs i steget Agera anropas sidmodellens GetMessagesAsync metod.

Akt-steg i enhetstest (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

IndexPage sidmodellens OnGetAsync metod (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Metoden GetMessagesAsync i DAL returnerar inte resultatet för det här metodanropet. Den simulerade versionen av metoden returnerar resultatet.

I Assert-steget tilldelas de faktiska meddelandena (actualMessages) från sidmodellens Messages-egenskap. En typkontroll utförs också när meddelandena tilldelas. De förväntade och faktiska meddelandena jämförs med deras Text egenskaper. Testet hävdar att de två List<Message> instanserna innehåller samma meddelanden.

// 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));

Andra tester i den här gruppen skapar sidmodellobjekt som innehåller DefaultHttpContext, ModelStateDictionary, en ActionContext för att upprätta PageContext, en ViewDataDictionaryoch en PageContext. Dessa är användbara för att utföra tester. Meddelandeappen upprättar till exempel ett ModelState fel med AddModelError för att kontrollera att ett giltigt PageResult returneras när OnPostAddMessageAsync körs:

[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);
}

Ytterligare resurser