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:

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 e Pages/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) e Text (messaggio). La Text 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 (AppDbContextData/AppDbContext.cs). I metodi DAL sono contrassegnati come virtual, 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
  • DataAccessLayerTest.cs contiene gli unit test per DAL.
  • IndexPageTests.cs contiene gli unit test per il modello di pagina Indice.
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:

  1. Arrange: il database è configurato per il test e/o il risultato previsto è definito.
  2. Act: il test viene eseguito.
  3. 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 .Id1 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

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:

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 e Pages/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) e Text (messaggio). La Text 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 (AppDbContextData/AppDbContext.cs). I metodi DAL sono contrassegnati come virtual, 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
  • DataAccessLayerTest.cs contiene gli unit test per DAL.
  • IndexPageTests.cs contiene gli unit test per il modello di pagina Indice.
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:

  1. Arrange: il database è configurato per il test e/o il risultato previsto è definito.
  2. Act: il test viene eseguito.
  3. 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 .Id1 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