Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
ASP.NET Core ondersteunt eenheidstests van Razor Pagina-apps. Tests van de DATA Access Layer (DAL) en paginamodellen helpen het volgende te garanderen:
- Onderdelen van een Razor Pagina-app werken onafhankelijk en samen als een eenheid tijdens het bouwen van apps.
- Klassen en methoden hebben beperkte verantwoordelijkheidsgebieden.
- Er bestaat aanvullende documentatie over hoe de app zich moet gedragen.
- Regressies, die fouten zijn veroorzaakt door updates van de code, worden gevonden tijdens geautomatiseerd bouwen en implementeren.
In dit onderwerp gaan we ervan uit dat je basiskennis hebt van Razor Pages-apps en unittests. Als u niet bekend bent met Razor Pages-apps of testconcepten, raadpleeg dan de volgende onderwerpen:
- Razor Paginaarchitectuur en -concepten in ASP.NET Core
- Zelfstudie: Aan de slag met Razor Pagina's in ASP.NET Core-
-
Testen met
dotnet test
Voorbeeldcode bekijken of downloaden (hoe download je)
Het voorbeeldproject bestaat uit twee apps:
| App | Projectmap | Description |
|---|---|---|
| Bericht-app | src/RazorPagesTestSample | Hiermee kan een gebruiker een bericht toevoegen, één bericht verwijderen, alle berichten verwijderen en berichten analyseren (het gemiddelde aantal woorden per bericht zoeken). |
| App testen | tests/RazorPagesTestSample.Tests | Wordt gebruikt om de DAL en het Indexpagina Model van de bericht-app te testen. |
De tests kunnen worden uitgevoerd met behulp van de ingebouwde testfuncties van een IDE, zoals Visual Studio. Als u Visual Studio Code of de opdrachtregel gebruikt, voert u de volgende opdracht uit bij een opdrachtprompt in de map tests/RazorPagesTestSample.Tests :
dotnet test
Organisatie van berichten-app
De berichten-app is een Pages-berichtensysteem met de volgende kenmerken:
- De indexpagina van de app (
Pages/Index.cshtmlenPages/Index.cshtml.cs) biedt een ui- en paginamodelmethode om de toevoeging, verwijdering en analyse van berichten te beheren (het gemiddelde aantal woorden per bericht zoeken). - Een bericht wordt beschreven door de
Messageklasse (Data/Message.cs) met twee eigenschappen:Id(sleutel) enText(bericht). DeTexteigenschap is vereist en beperkt tot 200 tekens. - Berichten worden opgeslagen met behulp van de in-memory database van Entity Framework†.
- De app bevat een DAL in de contextklasse van de database,
AppDbContext(Data/AppDbContext.cs). De DAL-methoden zijn gemarkeerdvirtual, waardoor de methoden voor gebruik in de tests kunnen worden gesimuleerd. - Als de database leeg is bij het opstarten van de app, wordt het berichtenarchief geïnitialiseerd met drie berichten. Deze seeded berichten worden ook gebruikt in tests.
†De EF-onderwerp, Testen met InMemory, legt uit hoe u een in-memory database gebruikt voor tests met MSTest. In dit onderwerp wordt het xUnit-testframework gebruikt. Testconcepten en test-implementaties in verschillende testframeworks zijn vergelijkbaar, maar niet identiek.
Hoewel de voorbeeld-app het opslagplaatspatroon niet gebruikt en geen effectief voorbeeld is van het UoW-patroon (Unit of Work),Razor ondersteunt Pages deze patronen van ontwikkeling. Zie De infrastructuurpersistentielaag en testcontrollerlogica ontwerpen in ASP.NET Core (het voorbeeld implementeert het opslagplaatspatroon).
App-organisatie testen
De test-app is een console-app in de map Tests/RazorPagesTestSample.Tests .
| App-map testen | Description |
|---|---|
| UnitTests |
|
| Utilities | Bevat de methode die wordt gebruikt voor het TestDbContextOptions maken van nieuwe databasecontextopties voor elke DAL-eenheidstest, zodat de database opnieuw wordt ingesteld op de basislijnvoorwaarde voor elke test. |
Het testframework is xUnit. Het framework voor het simuleren van objecten is Moq.
Eenheidstests van de data access layer (DAL)
De berichten-app heeft een DAL met vier methoden in de AppDbContext klasse (src/RazorPagesTestSample/Data/AppDbContext.cs). Elke methode heeft een of twee eenheidstests in de test-app.
| DAL-methode | Function |
|---|---|
GetMessagesAsync |
Haalt een List<Message> uit de database, gesorteerd op de eigenschap Text. |
AddMessageAsync |
Voegt een Message aan de database toe. |
DeleteAllMessagesAsync |
Hiermee verwijdert u alle Message vermeldingen uit de database. |
DeleteMessageAsync |
Verwijdert een enkel Message uit de database via Id. |
Unittests van de DAL vereisen DbContextOptions bij het creëren van een nieuwe AppDbContext voor elke test. Een benadering voor het maken van de DbContextOptions voor elke test is het gebruik van een DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Het probleem met deze benadering is dat elke test de database ontvangt met de status van de vorige test. Dit kan problematisch zijn bij het schrijven van atomische eenheidstests die elkaar niet verstoren. Als u wilt afdwingen dat voor AppDbContext elke test een nieuwe databasecontext wordt gebruikt, geeft u een DbContextOptions exemplaar op dat is gebaseerd op een nieuwe serviceprovider. De test-app laat zien hoe u dit doet met behulp van de Utilities klassemethode 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;
}
Door de DbContextOptions in de DAL-eenheidstests te gebruiken, kan elke test atomisch worden uitgevoerd met een nieuw database-exemplaar:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Elke testmethode in de DataAccessLayerTest klasse (UnitTests/DataAccessLayerTest.cs) volgt een vergelijkbaar rangschikken-Act-Assert patroon:
- Rangschikken: De database is geconfigureerd voor de test en/of het verwachte resultaat is gedefinieerd.
- Act: De test wordt uitgevoerd.
- Assertie: Asserties worden gemaakt om te bepalen of het testresultaat een succes is.
Bijvoorbeeld, de DeleteMessageAsync methode is verantwoordelijk voor het verwijderen van één enkel bericht dat wordt geïdentificeerd door zijn 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();
}
}
Er zijn twee tests voor deze methode. Met één test wordt gecontroleerd of met de methode een bericht wordt verwijderd wanneer het bericht aanwezig is in de database. Met de andere methode wordt getest dat de database niet wordt gewijzigd als het bericht Id voor verwijdering niet bestaat. De DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound methode wordt hieronder weergegeven:
[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));
}
}
Eerst voert de methode de rangschikkingsstap uit, waarbij de voorbereiding voor de Act-stap plaatsvindt. De seeding-berichten worden verkregen en bewaard in seedMessages. De seeding-berichten worden opgeslagen in de database. Het bericht met een Id van 1 is ingesteld voor verwijdering. Wanneer de DeleteMessageAsync methode wordt uitgevoerd, moeten de verwachte berichten alle berichten bevatten, met uitzondering van de berichten met een Id van 1. De expectedMessages variabele vertegenwoordigt dit verwachte resultaat.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
De methode fungeert: de DeleteMessageAsync methode wordt uitgevoerd door het doorgeven van recId:1
// Act
await db.DeleteMessageAsync(recId);
Ten slotte verkrijgt de methode de Messages uit de context en vergelijkt het met de expectedMessages om te bevestigen dat de twee gelijk zijn:
// 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));
Om te vergelijken dat de twee List<Message> hetzelfde zijn:
- De berichten worden geordend op
Id. - Berichtparen worden op de
Texteigenschap vergeleken.
Een vergelijkbare testmethode DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound controleert het resultaat van het verwijderen van een bericht dat niet bestaat. In dit geval moeten de verwachte berichten in de database gelijk zijn aan de werkelijke berichten nadat de DeleteMessageAsync methode is uitgevoerd. De inhoud van de database mag niet worden gewijzigd:
[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));
}
}
Eenheidstests van de paginamodelmethoden
Een andere set eenheidstests is verantwoordelijk voor tests van paginamodelmethoden. In de bericht-app vindt u de indexpaginamodellen in de IndexModel klasse in src/RazorPagesTestSample/Pages/Index.cshtml.cs.
| Methode paginamodel | Function |
|---|---|
OnGetAsync |
Haalt de berichten van de DAL voor de gebruikersinterface op met behulp van de GetMessagesAsync methode. |
OnPostAddMessageAsync |
Als de ModelState geldig is, roept u AddMessageAsync aan om een bericht toe te voegen aan de database. |
OnPostDeleteAllMessagesAsync |
Aanroepen DeleteAllMessagesAsync om alle berichten in de database te verwijderen. |
OnPostDeleteMessageAsync |
DeleteMessageAsync Wordt uitgevoerd om een bericht met de opgegeven waarde Id te verwijderen. |
OnPostAnalyzeMessagesAsync |
Als een of meer berichten zich in de database bevinden, berekent u het gemiddelde aantal woorden per bericht. |
De methoden voor paginamodellen worden getest met behulp van zeven tests in de IndexPageTests klasse (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). De tests maken gebruik van het vertrouwde patroon Arrange-Act-Assert. Deze tests zijn gericht op:
- Bepalen of de methoden het juiste gedrag volgen wanneer de ModelState ongeldig is.
- Bevestig dat de methoden de juiste IActionResultproduceren.
- Controleren of eigenschapswaardetoewijzingen correct zijn gemaakt.
In deze groep tests worden vaak de methoden van de DAL gesimuleerd om verwachte gegevens te produceren voor de Act-stap waarin een paginamodelmethode wordt uitgevoerd. De GetMessagesAsync-methode van de AppDbContext wordt bijvoorbeeld gemockt om uitvoer te produceren. Wanneer een paginamodelmethode deze methode uitvoert, retourneert de mock het resultaat. De gegevens zijn niet afkomstig uit de database. Hierdoor ontstaan voorspelbare, betrouwbare testvoorwaarden voor het gebruik van de DAL in de paginamodeltests.
De OnGetAsync_PopulatesThePageModel_WithAListOfMessages test laat zien hoe de GetMessagesAsync methode wordt gesimuleerd voor het paginamodel:
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);
Wanneer de OnGetAsync methode in de Act-stap wordt uitgevoerd, wordt de GetMessagesAsync methode van het paginamodel opgeroepen.
Stap eenheidstest act (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
IndexPage methode van het OnGetAsync paginamodel (src/RazorPagesTestSample/Pages/Index.cshtml.cs):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
De GetMessagesAsync methode in de DAL retourneert het resultaat voor deze methode-aanroep niet. De gemockte versie van de methode geeft het resultaat terug.
In de Assert stap worden de werkelijke berichten (actualMessages) toegewezen vanuit de Messages eigenschap van het paginamodel. Er wordt ook een typecontrole uitgevoerd wanneer de berichten worden toegewezen. De verwachte en werkelijke berichten worden vergeleken met hun Text eigenschappen. De test bevestigt dat de twee List<Message> exemplaren dezelfde berichten bevatten.
// 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));
Andere tests in deze groep maken paginamodelobjecten met de DefaultHttpContext, de ModelStateDictionary, een ActionContext om de PageContext, een ViewDataDictionary en een PageContext. Deze zijn handig bij het uitvoeren van tests. Met de bericht-app wordt bijvoorbeeld een ModelState fout gecreëerd met AddModelError om te controleren of er een geldige PageResult wordt geretourneerd wanneer OnPostAddMessageAsync wordt uitgevoerd:
[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);
}
Aanvullende bronnen
ASP.NET Core ondersteunt eenheidstests van Razor Pagina-apps. Tests van de DATA Access Layer (DAL) en paginamodellen helpen het volgende te garanderen:
- Onderdelen van een Razor Pagina-app werken onafhankelijk en samen als een eenheid tijdens het bouwen van apps.
- Klassen en methoden hebben beperkte verantwoordelijkheidsgebieden.
- Er bestaat aanvullende documentatie over hoe de app zich moet gedragen.
- Regressies, die fouten zijn veroorzaakt door updates van de code, worden gevonden tijdens geautomatiseerd bouwen en implementeren.
In dit onderwerp gaan we ervan uit dat je basiskennis hebt van Razor Pages-apps en unittests. Als u niet bekend bent met Razor Pages-apps of testconcepten, raadpleeg dan de volgende onderwerpen:
- Razor Paginaarchitectuur en -concepten in ASP.NET Core
- Zelfstudie: Aan de slag met Razor Pagina's in ASP.NET Core-
-
Testen met
dotnet test
Voorbeeldcode bekijken of downloaden (hoe download je)
Het voorbeeldproject bestaat uit twee apps:
| App | Projectmap | Description |
|---|---|---|
| Bericht-app | src/RazorPagesTestSample | Hiermee kan een gebruiker een bericht toevoegen, één bericht verwijderen, alle berichten verwijderen en berichten analyseren (het gemiddelde aantal woorden per bericht zoeken). |
| App testen | tests/RazorPagesTestSample.Tests | Wordt gebruikt om de DAL en het Indexpagina Model van de bericht-app te testen. |
De tests kunnen worden uitgevoerd met behulp van de ingebouwde testfuncties van een IDE, zoals Visual Studio. Als u Visual Studio Code of de opdrachtregel gebruikt, voert u de volgende opdracht uit bij een opdrachtprompt in de map tests/RazorPagesTestSample.Tests :
dotnet test
Organisatie van berichten-app
De berichten-app is een Pages-berichtensysteem met de volgende kenmerken:
- De indexpagina van de app (
Pages/Index.cshtmlenPages/Index.cshtml.cs) biedt een ui- en paginamodelmethode om de toevoeging, verwijdering en analyse van berichten te beheren (het gemiddelde aantal woorden per bericht zoeken). - Een bericht wordt beschreven door de
Messageklasse (Data/Message.cs) met twee eigenschappen:Id(sleutel) enText(bericht). DeTexteigenschap is vereist en beperkt tot 200 tekens. - Berichten worden opgeslagen met behulp van de in-memory database van Entity Framework†.
- De app bevat een DAL in de contextklasse van de database,
AppDbContext(Data/AppDbContext.cs). De DAL-methoden zijn gemarkeerdvirtual, waardoor de methoden voor gebruik in de tests kunnen worden gesimuleerd. - Als de database leeg is bij het opstarten van de app, wordt het berichtenarchief geïnitialiseerd met drie berichten. Deze seeded berichten worden ook gebruikt in tests.
†De EF-onderwerp, Testen met InMemory, legt uit hoe u een in-memory database gebruikt voor tests met MSTest. In dit onderwerp wordt het xUnit-testframework gebruikt. Testconcepten en test-implementaties in verschillende testframeworks zijn vergelijkbaar, maar niet identiek.
Hoewel de voorbeeld-app het opslagplaatspatroon niet gebruikt en geen effectief voorbeeld is van het UoW-patroon (Unit of Work),Razor ondersteunt Pages deze patronen van ontwikkeling. Zie De infrastructuurpersistentielaag en testcontrollerlogica ontwerpen in ASP.NET Core (het voorbeeld implementeert het opslagplaatspatroon).
App-organisatie testen
De test-app is een console-app in de map Tests/RazorPagesTestSample.Tests .
| App-map testen | Description |
|---|---|
| UnitTests |
|
| Utilities | Bevat de methode die wordt gebruikt voor het TestDbContextOptions maken van nieuwe databasecontextopties voor elke DAL-eenheidstest, zodat de database opnieuw wordt ingesteld op de basislijnvoorwaarde voor elke test. |
Het testframework is xUnit. Het framework voor het simuleren van objecten is Moq.
Eenheidstests van de data access layer (DAL)
De berichten-app heeft een DAL met vier methoden in de AppDbContext klasse (src/RazorPagesTestSample/Data/AppDbContext.cs). Elke methode heeft een of twee eenheidstests in de test-app.
| DAL-methode | Function |
|---|---|
GetMessagesAsync |
Haalt een List<Message> uit de database, gesorteerd op de eigenschap Text. |
AddMessageAsync |
Voegt een Message aan de database toe. |
DeleteAllMessagesAsync |
Hiermee verwijdert u alle Message vermeldingen uit de database. |
DeleteMessageAsync |
Verwijdert een enkel Message uit de database via Id. |
Unittests van de DAL vereisen DbContextOptions bij het creëren van een nieuwe AppDbContext voor elke test. Een benadering voor het maken van de DbContextOptions voor elke test is het gebruik van een DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Het probleem met deze benadering is dat elke test de database ontvangt met de status van de vorige test. Dit kan problematisch zijn bij het schrijven van atomische eenheidstests die elkaar niet verstoren. Als u wilt afdwingen dat voor AppDbContext elke test een nieuwe databasecontext wordt gebruikt, geeft u een DbContextOptions exemplaar op dat is gebaseerd op een nieuwe serviceprovider. De test-app laat zien hoe u dit doet met behulp van de Utilities klassemethode 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;
}
Door de DbContextOptions in de DAL-eenheidstests te gebruiken, kan elke test atomisch worden uitgevoerd met een nieuw database-exemplaar:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Elke testmethode in de DataAccessLayerTest klasse (UnitTests/DataAccessLayerTest.cs) volgt een vergelijkbaar rangschikken-Act-Assert patroon:
- Rangschikken: De database is geconfigureerd voor de test en/of het verwachte resultaat is gedefinieerd.
- Act: De test wordt uitgevoerd.
- Assertie: Asserties worden gemaakt om te bepalen of het testresultaat een succes is.
Bijvoorbeeld, de DeleteMessageAsync methode is verantwoordelijk voor het verwijderen van één enkel bericht dat wordt geïdentificeerd door zijn 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();
}
}
Er zijn twee tests voor deze methode. Met één test wordt gecontroleerd of met de methode een bericht wordt verwijderd wanneer het bericht aanwezig is in de database. Met de andere methode wordt getest dat de database niet wordt gewijzigd als het bericht Id voor verwijdering niet bestaat. De DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound methode wordt hieronder weergegeven:
[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));
}
}
Eerst voert de methode de rangschikkingsstap uit, waarbij de voorbereiding voor de Act-stap plaatsvindt. De seeding-berichten worden verkregen en bewaard in seedMessages. De seeding-berichten worden opgeslagen in de database. Het bericht met een Id van 1 is ingesteld voor verwijdering. Wanneer de DeleteMessageAsync methode wordt uitgevoerd, moeten de verwachte berichten alle berichten bevatten, met uitzondering van de berichten met een Id van 1. De expectedMessages variabele vertegenwoordigt dit verwachte resultaat.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
De methode fungeert: de DeleteMessageAsync methode wordt uitgevoerd door het doorgeven van recId:1
// Act
await db.DeleteMessageAsync(recId);
Ten slotte verkrijgt de methode de Messages uit de context en vergelijkt het met de expectedMessages om te bevestigen dat de twee gelijk zijn:
// 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));
Om te vergelijken dat de twee List<Message> hetzelfde zijn:
- De berichten worden geordend op
Id. - Berichtparen worden op de
Texteigenschap vergeleken.
Een vergelijkbare testmethode DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound controleert het resultaat van het verwijderen van een bericht dat niet bestaat. In dit geval moeten de verwachte berichten in de database gelijk zijn aan de werkelijke berichten nadat de DeleteMessageAsync methode is uitgevoerd. De inhoud van de database mag niet worden gewijzigd:
[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));
}
}
Eenheidstests van de paginamodelmethoden
Een andere set eenheidstests is verantwoordelijk voor tests van paginamodelmethoden. In de bericht-app vindt u de indexpaginamodellen in de IndexModel klasse in src/RazorPagesTestSample/Pages/Index.cshtml.cs.
| Methode paginamodel | Function |
|---|---|
OnGetAsync |
Haalt de berichten van de DAL voor de gebruikersinterface op met behulp van de GetMessagesAsync methode. |
OnPostAddMessageAsync |
Als de ModelState geldig is, roept u AddMessageAsync aan om een bericht toe te voegen aan de database. |
OnPostDeleteAllMessagesAsync |
Aanroepen DeleteAllMessagesAsync om alle berichten in de database te verwijderen. |
OnPostDeleteMessageAsync |
DeleteMessageAsync Wordt uitgevoerd om een bericht met de opgegeven waarde Id te verwijderen. |
OnPostAnalyzeMessagesAsync |
Als een of meer berichten zich in de database bevinden, berekent u het gemiddelde aantal woorden per bericht. |
De methoden voor paginamodellen worden getest met behulp van zeven tests in de IndexPageTests klasse (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). De tests maken gebruik van het vertrouwde patroon Rangschikken-Handelen-Controle. Deze tests zijn gericht op:
- Bepalen of de methoden het juiste gedrag volgen wanneer de ModelState ongeldig is.
- Bevestig dat de methoden de juiste IActionResultproduceren.
- Controleren of eigenschapswaardetoewijzingen correct zijn gemaakt.
In deze groep tests worden vaak de methoden van de DAL gesimuleerd om verwachte gegevens te produceren voor de Act-stap waarin een paginamodelmethode wordt uitgevoerd. De GetMessagesAsync-methode van de AppDbContext wordt bijvoorbeeld gemockt om uitvoer te produceren. Wanneer een paginamodelmethode deze methode uitvoert, retourneert de mock het resultaat. De gegevens zijn niet afkomstig uit de database. Hierdoor ontstaan voorspelbare, betrouwbare testvoorwaarden voor het gebruik van de DAL in de paginamodeltests.
De OnGetAsync_PopulatesThePageModel_WithAListOfMessages test laat zien hoe de GetMessagesAsync methode wordt gesimuleerd voor het paginamodel:
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);
Wanneer de OnGetAsync methode in de Act-stap wordt uitgevoerd, wordt de GetMessagesAsync methode van het paginamodel opgeroepen.
Stap eenheidstest act (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):
// Act
await pageModel.OnGetAsync();
IndexPage methode van het OnGetAsync paginamodel (src/RazorPagesTestSample/Pages/Index.cshtml.cs):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
De GetMessagesAsync methode in de DAL retourneert het resultaat voor deze methode-aanroep niet. De gemockte versie van de methode geeft het resultaat terug.
In de Assert stap worden de werkelijke berichten (actualMessages) toegewezen vanuit de Messages eigenschap van het paginamodel. Er wordt ook een typecontrole uitgevoerd wanneer de berichten worden toegewezen. De verwachte en werkelijke berichten worden vergeleken met hun Text eigenschappen. De test bevestigt dat de twee List<Message> exemplaren dezelfde berichten bevatten.
// 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));
Andere tests in deze groep maken paginamodelobjecten met de DefaultHttpContext, de ModelStateDictionary, een ActionContext om de PageContext, een ViewDataDictionary en een PageContext. Deze zijn handig bij het uitvoeren van tests. Met de bericht-app wordt bijvoorbeeld een ModelState fout gecreëerd met AddModelError om te controleren of er een geldige PageResult wordt geretourneerd wanneer OnPostAddMessageAsync wordt uitgevoerd:
[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);
}
Aanvullende bronnen
-
Testen met
dotnet test - Controllerlogica testen in ASP.NET Core
- Unit Test Your Code (Visual Studio)
- Integratietests in ASP.NET Core
- xUnit.net
- Aan de slag met xUnit.net
- Moq
- Moq Quickstart
- JustMockLite: een mocking framework voor .NET-ontwikkelaars. (Niet onderhouden of ondersteund door Microsoft.)