Razor Pagine di unit test in ASP.NET Core
ASP.NET Core supporta unit test delle Razor app Pages. I test del livello di accesso ai dati (DAL) e dei modelli di pagina consentono di garantire:
- Parti di un'app Razor Pages funzionano in modo indipendente e insieme come unità durante la costruzione dell'app.
- Le classi e i metodi hanno ambiti di responsabilità limitati.
- È disponibile una documentazione aggiuntiva sul comportamento dell'app.
- Le regressioni, che sono errori generati dagli aggiornamenti del codice, vengono trovate durante la compilazione e la distribuzione automatizzate.
In questo argomento si presuppone che si abbia una conoscenza di base delle Razor app Pages e degli unit test. Se non si ha familiarità con Razor le app Pages o i concetti di test, vedere gli argomenti seguenti:
- Introduzione alle Razor pagine in ASP.NET Core
- Esercitazione: Introduzione all'uso di Pages in ASP.NET CoreRazor
- Unit test di C# in .NET Core usando il test dotnet e xUnit
Visualizzare o scaricare il codice di esempio (procedura per il download)
Il progetto di esempio è costituito da due app:
App | Cartella del progetto | Descrizione |
---|---|---|
App messaggio | src/RazorPagesTestSample | Consente a un utente di aggiungere un messaggio, eliminare un messaggio, eliminare tutti i messaggi e analizzare i messaggi (trovare il numero medio di parole per messaggio). |
Testare l'app | tests/RazorPagesTestSample.Tests | Usato per eseguire unit test del modello di pagina DAL e Index dell'app per i messaggi. |
I test possono essere eseguiti usando le funzionalità di test predefinite di un IDE, ad esempio Visual Studio. Se si usa Visual Studio Code o la riga di comando, eseguire il comando seguente al prompt dei comandi nella cartella tests/RazorPagesTestSample.Tests :
dotnet test
Organizzazione dell'app messaggio
L'app messaggio è un Razor sistema di messaggi Pages con le caratteristiche seguenti:
- La pagina Indice dell'app (
Pages/Index.cshtml
ePages/Index.cshtml.cs
) fornisce metodi di interfaccia utente e modello di pagina per controllare l'aggiunta, l'eliminazione e l'analisi dei messaggi (trovare il numero medio di parole per messaggio). - Un messaggio viene descritto dalla
Message
classe (Data/Message.cs
) con due proprietà:Id
(chiave) eText
(messaggio). LaText
proprietà è obbligatoria e limitata a 200 caratteri. - I messaggi vengono archiviati usando il database in memoria di Entity Framework†.
- L'app contiene un dal nella relativa classe di contesto di database (
AppDbContext
Data/AppDbContext.cs
). I metodi DAL sono contrassegnati comevirtual
, che consentono di simulare i metodi da usare nei test. - Se il database è vuoto all'avvio dell'app, l'archivio messaggi viene inizializzato con tre messaggi. Questi messaggi di inizializzazione vengono usati anche nei test.
†Il test di Entity Framework con InMemory illustra come usare un database in memoria per i test con MSTest. Questo argomento usa il framework di test xUnit . I concetti di test e le implementazioni di test in framework di test diversi sono simili ma non identici.
Anche se l'app di esempio non usa il modello di repository e non è un esempio efficace del modello Unit of Work (UoW), Razor Pages supporta questi modelli di sviluppo. Per altre informazioni, vedere Progettazione del livello di persistenza dell'infrastruttura e Logica del controller di test in ASP.NET Core (l'esempio implementa il modello di repository).
Testare l'organizzazione dell'app
L'app di test è un'app console all'interno della cartella tests/RazorPagesTestSample.Tests .
Testare la cartella dell'app | Descrizione |
---|---|
UnitTests |
|
Utilità | Contiene il TestDbContextOptions metodo utilizzato per creare nuove opzioni di contesto del database per ogni unit test DAL in modo che il database venga reimpostato sulla relativa condizione di base per ogni test. |
Il framework di test è xUnit. Il framework di simulazione dell'oggetto è Moq.
Unit test del livello di accesso ai dati (DAL)
L'app per i messaggi ha un dal con quattro metodi contenuti nella AppDbContext
classe (src/RazorPagesTestSample/Data/AppDbContext.cs
). Ogni metodo ha uno o due unit test nell'app di test.
Dal, metodo | Funzione |
---|---|
GetMessagesAsync |
Ottiene un oggetto List<Message> dal database ordinato in base alla Text proprietà . |
AddMessageAsync |
Aggiunge un oggetto Message al database. |
DeleteAllMessagesAsync |
Elimina tutte le Message voci dal database. |
DeleteMessageAsync |
Elimina un singolo Message dal database da Id . |
Gli unit test di DAL richiedono DbContextOptions quando si crea un nuovo AppDbContext
test per ogni test. Un approccio alla creazione di per ogni test consiste nell'usare DbContextOptions
un oggetto DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Il problema con questo approccio è che ogni test riceve il database in qualsiasi stato il test precedente lo ha lasciato. Ciò può essere problematico quando si tenta di scrivere unit test atomici che non interferiscono tra loro. Per forzare l'utilizzo AppDbContext
di un nuovo contesto di database per ogni test, specificare un'istanza DbContextOptions
basata su un nuovo provider di servizi. L'app di test mostra come eseguire questa operazione usando il Utilities
metodo TestDbContextOptions
di classe (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;
}
L'uso di DbContextOptions
negli unit test DAL consente a ogni test di eseguire in modo atomico con un'istanza di database aggiornata:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Ogni metodo di test nella DataAccessLayerTest
classe (UnitTests/DataAccessLayerTest.cs
) segue un modello Arrange-Act-Assert simile:
- Arrange: il database è configurato per il test e/o il risultato previsto è definito.
- Act: il test viene eseguito.
- Assert: le asserzioni vengono effettuate per determinare se il risultato del test è un esito positivo.
Ad esempio, il DeleteMessageAsync
metodo è responsabile della rimozione di un singolo messaggio identificato dal relativo 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();
}
}
Esistono due test per questo metodo. Un test verifica che il metodo elimini un messaggio quando il messaggio è presente nel database. L'altro metodo verifica che il database non cambi se il messaggio Id
per l'eliminazione non esiste. Il DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
metodo è illustrato di seguito:
[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));
}
}
In primo luogo, il metodo esegue il passaggio Arrange, in cui viene eseguita la preparazione per il passaggio Act. I messaggi di seeding vengono ottenuti e mantenuti in seedMessages
. I messaggi di seeding vengono salvati nel database. Il messaggio con un Id
di 1
è impostato per l'eliminazione. Quando il DeleteMessageAsync
metodo viene eseguito, i messaggi previsti devono contenere tutti i messaggi ad eccezione di quello con .Id
1
La expectedMessages
variabile rappresenta questo risultato previsto.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
Il metodo agisce: il DeleteMessageAsync
metodo viene eseguito passando l'oggetto recId
di 1
:
// Act
await db.DeleteMessageAsync(recId);
Infine, il metodo ottiene dal Messages
contesto e lo confronta con l'asserzione expectedMessages
che i due sono uguali:
// 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));
Per confrontare che le due List<Message>
sono le stesse:
- I messaggi vengono ordinati in base a
Id
. - Le coppie di messaggi vengono confrontate sulla
Text
proprietà .
Un metodo di test simile controlla DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
il risultato del tentativo di eliminare un messaggio che non esiste. In questo caso, i messaggi previsti nel database devono essere uguali ai messaggi effettivi dopo l'esecuzione del DeleteMessageAsync
metodo. Non deve essere presente alcuna modifica al contenuto del database:
[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));
}
}
Unit test dei metodi del modello di pagina
Un altro set di unit test è responsabile dei test dei metodi del modello di pagina. Nell'app messaggio i modelli di pagina Indice si trovano nella IndexModel
classe in src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Metodo del modello di pagina | Funzione |
---|---|
OnGetAsync |
Ottiene i messaggi dal dal per l'interfaccia utente usando il GetMessagesAsync metodo . |
OnPostAddMessageAsync |
Se ModelState è valido, chiama AddMessageAsync per aggiungere un messaggio al database. |
OnPostDeleteAllMessagesAsync |
Chiama DeleteAllMessagesAsync per eliminare tutti i messaggi nel database. |
OnPostDeleteMessageAsync |
DeleteMessageAsync Esegue per eliminare un messaggio con l'oggetto Id specificato. |
OnPostAnalyzeMessagesAsync |
Se uno o più messaggi si trovano nel database, calcola il numero medio di parole per messaggio. |
I metodi del modello di pagina vengono testati usando sette test nella IndexPageTests
classe (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). I test usano il modello Arrange-Assert-Act familiare. Questi test si concentrano su:
- Determinare se i metodi seguono il comportamento corretto quando ModelState non è valido.
- La conferma dei metodi produce l'oggetto corretto IActionResult.
- Verifica che le assegnazioni dei valori della proprietà vengano eseguite correttamente.
Questo gruppo di test spesso simula i metodi del dal per produrre i dati previsti per il passaggio Act in cui viene eseguito un metodo del modello di pagina. Ad esempio, il GetMessagesAsync
metodo di viene simulato per produrre l'output AppDbContext
. Quando un metodo del modello di pagina esegue questo metodo, la simulazione restituisce il risultato. I dati non provengono dal database. In questo modo vengono create condizioni di test prevedibili e affidabili per l'uso di DAL nei test del modello di pagina.
Il OnGetAsync_PopulatesThePageModel_WithAListOfMessages
test mostra come viene simulato il GetMessagesAsync
metodo per il modello di pagina:
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);
Quando il OnGetAsync
metodo viene eseguito nel passaggio Act, chiama il metodo del modello di GetMessagesAsync
pagina.
Passaggio dell'atto di unit test (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
):
// Act
await pageModel.OnGetAsync();
IndexPage
metodo del modello di OnGetAsync
pagina (src/RazorPagesTestSample/Pages/Index.cshtml.cs
):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
Il GetMessagesAsync
metodo in DAL non restituisce il risultato per questa chiamata al metodo. La versione fittizia del metodo restituisce il risultato.
Assert
Nel passaggio i messaggi effettivi (actualMessages
) vengono assegnati dalla Messages
proprietà del modello di pagina. Quando vengono assegnati i messaggi, viene eseguito anche un controllo del tipo. I messaggi previsti e effettivi vengono confrontati in base alle relative Text
proprietà. Il test afferma che le due List<Message>
istanze contengono gli stessi messaggi.
// 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));
Altri test in questo gruppo creano oggetti modello di pagina che includono DefaultHttpContext, ModelStateDictionary, un ActionContext oggetto per stabilire PageContext
, e ViewDataDictionary
.PageContext
Questi sono utili per eseguire test. Ad esempio, l'app messaggio stabilisce un ModelState
errore con AddModelError per verificare che venga restituito un valore valido PageResult quando OnPostAddMessageAsync
viene eseguito:
[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);
}
Risorse aggiuntive
- Unit test di C# in .NET Core usando il test dotnet e xUnit
- Testare la logica del controller in ASP.NET Core
- Eseguire unit test del codice (Visual Studio)
- Test di integrazione in ASP.NET Core
- xUnit.net
- Introduzione a xUnit.net: Uso di .NET Core con la riga di comando di .NET SDK
- Moq
- Guida introduttiva a Moq
ASP.NET Core supporta unit test delle Razor app Pages. I test del livello di accesso ai dati (DAL) e dei modelli di pagina consentono di garantire:
- Parti di un'app Razor Pages funzionano in modo indipendente e insieme come unità durante la costruzione dell'app.
- Le classi e i metodi hanno ambiti di responsabilità limitati.
- È disponibile una documentazione aggiuntiva sul comportamento dell'app.
- Le regressioni, che sono errori generati dagli aggiornamenti del codice, vengono trovate durante la compilazione e la distribuzione automatizzate.
In questo argomento si presuppone che si abbia una conoscenza di base delle Razor app Pages e degli unit test. Se non si ha familiarità con Razor le app Pages o i concetti di test, vedere gli argomenti seguenti:
- Introduzione alle Razor pagine in ASP.NET Core
- Esercitazione: Introduzione all'uso di Pages in ASP.NET CoreRazor
- Unit test di C# in .NET Core usando il test dotnet e xUnit
Visualizzare o scaricare il codice di esempio (procedura per il download)
Il progetto di esempio è costituito da due app:
App | Cartella del progetto | Descrizione |
---|---|---|
App messaggio | src/RazorPagesTestSample | Consente a un utente di aggiungere un messaggio, eliminare un messaggio, eliminare tutti i messaggi e analizzare i messaggi (trovare il numero medio di parole per messaggio). |
Testare l'app | tests/RazorPagesTestSample.Tests | Usato per eseguire unit test del modello di pagina DAL e Index dell'app per i messaggi. |
I test possono essere eseguiti usando le funzionalità di test predefinite di un IDE, ad esempio Visual Studio. Se si usa Visual Studio Code o la riga di comando, eseguire il comando seguente al prompt dei comandi nella cartella tests/RazorPagesTestSample.Tests :
dotnet test
Organizzazione dell'app messaggio
L'app messaggio è un Razor sistema di messaggi Pages con le caratteristiche seguenti:
- La pagina Indice dell'app (
Pages/Index.cshtml
ePages/Index.cshtml.cs
) fornisce metodi di interfaccia utente e modello di pagina per controllare l'aggiunta, l'eliminazione e l'analisi dei messaggi (trovare il numero medio di parole per messaggio). - Un messaggio viene descritto dalla
Message
classe (Data/Message.cs
) con due proprietà:Id
(chiave) eText
(messaggio). LaText
proprietà è obbligatoria e limitata a 200 caratteri. - I messaggi vengono archiviati usando il database in memoria di Entity Framework†.
- L'app contiene un dal nella relativa classe di contesto di database (
AppDbContext
Data/AppDbContext.cs
). I metodi DAL sono contrassegnati comevirtual
, che consentono di simulare i metodi da usare nei test. - Se il database è vuoto all'avvio dell'app, l'archivio messaggi viene inizializzato con tre messaggi. Questi messaggi di inizializzazione vengono usati anche nei test.
†Il test di Entity Framework con InMemory illustra come usare un database in memoria per i test con MSTest. Questo argomento usa il framework di test xUnit . I concetti di test e le implementazioni di test in framework di test diversi sono simili ma non identici.
Anche se l'app di esempio non usa il modello di repository e non è un esempio efficace del modello Unit of Work (UoW), Razor Pages supporta questi modelli di sviluppo. Per altre informazioni, vedere Progettazione del livello di persistenza dell'infrastruttura e Logica del controller di test in ASP.NET Core (l'esempio implementa il modello di repository).
Testare l'organizzazione dell'app
L'app di test è un'app console all'interno della cartella tests/RazorPagesTestSample.Tests .
Testare la cartella dell'app | Descrizione |
---|---|
UnitTests |
|
Utilità | Contiene il TestDbContextOptions metodo utilizzato per creare nuove opzioni di contesto del database per ogni unit test DAL in modo che il database venga reimpostato sulla relativa condizione di base per ogni test. |
Il framework di test è xUnit. Il framework di simulazione dell'oggetto è Moq.
Unit test del livello di accesso ai dati (DAL)
L'app per i messaggi ha un dal con quattro metodi contenuti nella AppDbContext
classe (src/RazorPagesTestSample/Data/AppDbContext.cs
). Ogni metodo ha uno o due unit test nell'app di test.
Dal, metodo | Funzione |
---|---|
GetMessagesAsync |
Ottiene un oggetto List<Message> dal database ordinato in base alla Text proprietà . |
AddMessageAsync |
Aggiunge un oggetto Message al database. |
DeleteAllMessagesAsync |
Elimina tutte le Message voci dal database. |
DeleteMessageAsync |
Elimina un singolo Message dal database da Id . |
Gli unit test di DAL richiedono DbContextOptions quando si crea un nuovo AppDbContext
test per ogni test. Un approccio alla creazione di per ogni test consiste nell'usare DbContextOptions
un oggetto DbContextOptionsBuilder:
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Il problema con questo approccio è che ogni test riceve il database in qualsiasi stato il test precedente lo ha lasciato. Ciò può essere problematico quando si tenta di scrivere unit test atomici che non interferiscono tra loro. Per forzare l'utilizzo AppDbContext
di un nuovo contesto di database per ogni test, specificare un'istanza DbContextOptions
basata su un nuovo provider di servizi. L'app di test mostra come eseguire questa operazione usando il Utilities
metodo TestDbContextOptions
di classe (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;
}
L'uso di DbContextOptions
negli unit test DAL consente a ogni test di eseguire in modo atomico con un'istanza di database aggiornata:
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Ogni metodo di test nella DataAccessLayerTest
classe (UnitTests/DataAccessLayerTest.cs
) segue un modello Arrange-Act-Assert simile:
- Arrange: il database è configurato per il test e/o il risultato previsto è definito.
- Act: il test viene eseguito.
- Assert: le asserzioni vengono effettuate per determinare se il risultato del test è un esito positivo.
Ad esempio, il DeleteMessageAsync
metodo è responsabile della rimozione di un singolo messaggio identificato dal relativo 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();
}
}
Esistono due test per questo metodo. Un test verifica che il metodo elimini un messaggio quando il messaggio è presente nel database. L'altro metodo verifica che il database non cambi se il messaggio Id
per l'eliminazione non esiste. Il DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
metodo è illustrato di seguito:
[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));
}
}
In primo luogo, il metodo esegue il passaggio Arrange, in cui viene eseguita la preparazione per il passaggio Act. I messaggi di seeding vengono ottenuti e mantenuti in seedMessages
. I messaggi di seeding vengono salvati nel database. Il messaggio con un Id
di 1
è impostato per l'eliminazione. Quando il DeleteMessageAsync
metodo viene eseguito, i messaggi previsti devono contenere tutti i messaggi ad eccezione di quello con .Id
1
La expectedMessages
variabile rappresenta questo risultato previsto.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
Il metodo agisce: il DeleteMessageAsync
metodo viene eseguito passando l'oggetto recId
di 1
:
// Act
await db.DeleteMessageAsync(recId);
Infine, il metodo ottiene dal Messages
contesto e lo confronta con l'asserzione expectedMessages
che i due sono uguali:
// 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));
Per confrontare che le due List<Message>
sono le stesse:
- I messaggi vengono ordinati in base a
Id
. - Le coppie di messaggi vengono confrontate sulla
Text
proprietà .
Un metodo di test simile controlla DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
il risultato del tentativo di eliminare un messaggio che non esiste. In questo caso, i messaggi previsti nel database devono essere uguali ai messaggi effettivi dopo l'esecuzione del DeleteMessageAsync
metodo. Non deve essere presente alcuna modifica al contenuto del database:
[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));
}
}
Unit test dei metodi del modello di pagina
Un altro set di unit test è responsabile dei test dei metodi del modello di pagina. Nell'app messaggio i modelli di pagina Indice si trovano nella IndexModel
classe in src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Metodo del modello di pagina | Funzione |
---|---|
OnGetAsync |
Ottiene i messaggi dal dal per l'interfaccia utente usando il GetMessagesAsync metodo . |
OnPostAddMessageAsync |
Se ModelState è valido, chiama AddMessageAsync per aggiungere un messaggio al database. |
OnPostDeleteAllMessagesAsync |
Chiama DeleteAllMessagesAsync per eliminare tutti i messaggi nel database. |
OnPostDeleteMessageAsync |
DeleteMessageAsync Esegue per eliminare un messaggio con l'oggetto Id specificato. |
OnPostAnalyzeMessagesAsync |
Se uno o più messaggi si trovano nel database, calcola il numero medio di parole per messaggio. |
I metodi del modello di pagina vengono testati usando sette test nella IndexPageTests
classe (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). I test usano il modello Arrange-Assert-Act familiare. Questi test si concentrano su:
- Determinare se i metodi seguono il comportamento corretto quando ModelState non è valido.
- La conferma dei metodi produce l'oggetto corretto IActionResult.
- Verifica che le assegnazioni dei valori della proprietà vengano eseguite correttamente.
Questo gruppo di test spesso simula i metodi del dal per produrre i dati previsti per il passaggio Act in cui viene eseguito un metodo del modello di pagina. Ad esempio, il GetMessagesAsync
metodo di viene simulato per produrre l'output AppDbContext
. Quando un metodo del modello di pagina esegue questo metodo, la simulazione restituisce il risultato. I dati non provengono dal database. In questo modo vengono create condizioni di test prevedibili e affidabili per l'uso di DAL nei test del modello di pagina.
Il OnGetAsync_PopulatesThePageModel_WithAListOfMessages
test mostra come viene simulato il GetMessagesAsync
metodo per il modello di pagina:
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);
Quando il OnGetAsync
metodo viene eseguito nel passaggio Act, chiama il metodo del modello di GetMessagesAsync
pagina.
Passaggio dell'atto di unit test (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
):
// Act
await pageModel.OnGetAsync();
IndexPage
metodo del modello di OnGetAsync
pagina (src/RazorPagesTestSample/Pages/Index.cshtml.cs
):
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
Il GetMessagesAsync
metodo in DAL non restituisce il risultato per questa chiamata al metodo. La versione fittizia del metodo restituisce il risultato.
Assert
Nel passaggio i messaggi effettivi (actualMessages
) vengono assegnati dalla Messages
proprietà del modello di pagina. Quando vengono assegnati i messaggi, viene eseguito anche un controllo del tipo. I messaggi previsti e effettivi vengono confrontati in base alle relative Text
proprietà. Il test afferma che le due List<Message>
istanze contengono gli stessi messaggi.
// 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));
Altri test in questo gruppo creano oggetti modello di pagina che includono DefaultHttpContext, ModelStateDictionary, un ActionContext oggetto per stabilire PageContext
, e ViewDataDictionary
.PageContext
Questi sono utili per eseguire test. Ad esempio, l'app messaggio stabilisce un ModelState
errore con AddModelError per verificare che venga restituito un valore valido PageResult quando OnPostAddMessageAsync
viene eseguito:
[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);
}
Risorse aggiuntive
- Unit test di C# in .NET Core usando il test dotnet e xUnit
- Testare la logica del controller in ASP.NET Core
- Eseguire unit test del codice (Visual Studio)
- Test di integrazione in ASP.NET Core
- xUnit.net
- Introduzione a xUnit.net: Uso di .NET Core con la riga di comando di .NET SDK
- Moq
- Guida introduttiva a Moq
- JustMockLite: framework fittizio per sviluppatori .NET. (Non gestito o supportato da Microsoft).