Razor Testy jednostkowe stron w programie ASP.NET Core

ASP.NET Core obsługuje testy jednostkowe Razor aplikacji Pages. Testy warstwy dostępu do danych (DAL) i modeli stron pomagają zapewnić:

  • Razor Części aplikacji Pages działają niezależnie i razem jako jednostka podczas budowy aplikacji.
  • Klasy i metody mają ograniczone zakresy odpowiedzialności.
  • Dodatkowa dokumentacja istnieje na temat sposobu działania aplikacji.
  • Regresje, które są błędami spowodowanymi aktualizacjami kodu, są znajdowane podczas zautomatyzowanego kompilowania i wdrażania.

W tym temacie założono, że masz podstawową wiedzę na temat Razor aplikacji Pages i testów jednostkowych. Jeśli nie Razor znasz aplikacji Stron lub pojęć dotyczących testowania, zobacz następujące tematy:

Wyświetl lub pobierz przykładowy kod (jak pobrać)

Przykładowy projekt składa się z dwóch aplikacji:

App Folder projektu opis
Aplikacja komunikatów src/RazorPagesTestSample Umożliwia użytkownikowi dodawanie komunikatu, usuwanie jednej wiadomości, usuwanie wszystkich komunikatów i analizowanie komunikatów (znajdowanie średniej liczby słów na wiadomość).
Testowanie aplikacji tests/RazorPagesTestSample.Tests Służy do testowania jednostkowego modelu strony DAL i indeksu aplikacji komunikatów.

Testy można uruchamiać przy użyciu wbudowanych funkcji testowych środowiska IDE, takich jak Visual Studio. Jeśli używasz programu Visual Studio Code lub wiersza polecenia, wykonaj następujące polecenie w wierszu polecenia w folderze tests/RazorPagesTestSample.Tests :

dotnet test

Organizacja aplikacji komunikatów

Aplikacja komunikatów to Razor system komunikatów Pages o następujących cechach:

  • Strona Indeks aplikacji (Pages/Index.cshtml i Pages/Index.cshtml.cs) udostępnia metody interfejsu użytkownika i modelu strony do kontrolowania dodawania, usuwania i analizowania komunikatów (znajdowanie średniej liczby słów na komunikat).
  • Komunikat jest opisany przez klasę Message (Data/Message.cs) z dwiema właściwościami: Id (klucz) i Text (komunikat). Właściwość jest wymagana Text i ograniczona do 200 znaków.
  • Komunikaty są przechowywane przy użyciu bazy danych w pamięci programu Entity Framework†.
  • Aplikacja zawiera dal w swojej klasie AppDbContext kontekstu bazy danych (Data/AppDbContext.cs). Metody DAL są oznaczone jako virtual, co umożliwia wyśmiewanie metod do użycia w testach.
  • Jeśli baza danych jest pusta podczas uruchamiania aplikacji, magazyn komunikatów jest inicjowany z trzema komunikatami. Te komunikaty rozmieszczane są również używane w testach.

† Temat ef, Test with InMemory, wyjaśnia, jak używać bazy danych w pamięci do testów przy użyciu narzędzia MSTest. W tym temacie jest używana struktura testowa xUnit . Koncepcje testów i implementacje testów w różnych platformach testowych są podobne, ale nie identyczne.

Mimo że przykładowa aplikacja nie używa wzorca repozytorium i nie jest skutecznym przykładem wzorca Unit of Work (UoW), Razor strony obsługują te wzorce programowania. Aby uzyskać więcej informacji, zobacz Projektowanie warstwy trwałości infrastruktury i logiki kontrolera testów w programie ASP.NET Core (przykład implementuje wzorzec repozytorium).

Testowanie organizacji aplikacji

Aplikacja testowa jest aplikacją konsolową w folderze tests/RazorPagesTestSample.Tests .

Folder aplikacji testowej opis
UnitTests
  • DataAccessLayerTest.cs zawiera testy jednostkowe dal.
  • IndexPageTests.cs zawiera testy jednostkowe modelu strony indeksowania.
Narzędzia Zawiera metodę TestDbContextOptions używaną do tworzenia nowych opcji kontekstu bazy danych dla każdego testu jednostkowego DAL, dzięki czemu baza danych zostanie zresetowana do stanu odniesienia dla każdego testu.

Struktura testowa to xUnit. Struktura pozorowania obiektów to Moq.

Testy jednostkowe warstwy dostępu do danych (DAL)

Aplikacja komunikatów ma dal z czterema metodami zawartymi AppDbContext w klasie (src/RazorPagesTestSample/Data/AppDbContext.cs). Każda metoda ma jeden lub dwa testy jednostkowe w aplikacji testowej.

DAL, metoda Function
GetMessagesAsync Uzyskuje element List<Message> z bazy danych posortowany według Text właściwości .
AddMessageAsync Dodaje element Message do bazy danych.
DeleteAllMessagesAsync Usuwa wszystkie Message wpisy z bazy danych.
DeleteMessageAsync Usuwa pojedynczy element Message z bazy danych za pomocą polecenia Id.

Testy jednostkowe dal wymagają DbContextOptions utworzenia nowego AppDbContext dla każdego testu. Jednym z podejść do tworzenia DbContextOptions elementu dla każdego testu jest użycie elementu DbContextOptionsBuilder:

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

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

Problem z tym podejściem polega na tym, że każdy test odbiera bazę danych w każdym stanie, w jakim opuścił poprzedni test. Może to być problematyczne podczas próby zapisania niepodzielnych testów jednostkowych, które nie zakłócają siebie nawzajem. Aby wymusić AppDbContext użycie nowego kontekstu bazy danych dla każdego testu, podaj DbContextOptions wystąpienie oparte na nowym dostawcy usług. Aplikacja testowa pokazuje, jak to zrobić przy użyciu metody UtilitiesTestDbContextOptions klasy (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;
}

Użycie w DbContextOptions testach jednostkowych DAL umożliwia wykonywanie każdego testu niepodziealnie przy użyciu nowego wystąpienia bazy danych:

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

Każda metoda testowa w DataAccessLayerTest klasie (UnitTests/DataAccessLayerTest.cs) jest zgodna z podobnym wzorcem Arrange-Act-Assert:

  1. Rozmieść: baza danych jest skonfigurowana dla testu i/lub oczekiwany wynik jest definiowany.
  2. Act: Test jest wykonywany.
  3. Asercji: asercji są tworzone w celu określenia, czy wynik testu jest sukcesem.

Na przykład DeleteMessageAsync metoda jest odpowiedzialna za usunięcie pojedynczego komunikatu zidentyfikowanego przez element 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();
    }
}

Istnieją dwa testy dla tej metody. Jeden test sprawdza, czy metoda usuwa komunikat, gdy komunikat znajduje się w bazie danych. Inna metoda sprawdza, czy baza danych nie zmienia się, jeśli komunikat Id o usunięciu nie istnieje. Poniżej DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound przedstawiono metodę:

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

Najpierw metoda wykonuje krok Rozmieść, w którym odbywa się przygotowanie do wykonania kroku aktu. Komunikaty rozmieszczania są uzyskiwane i przechowywane w elemencie seedMessages. Komunikaty rozmieszczania są zapisywane w bazie danych. Komunikat z elementem Id1 jest ustawiony do usunięcia. Po wykonaniu DeleteMessageAsync metody oczekiwane komunikaty powinny mieć wszystkie komunikaty z wyjątkiem tego, który ma Id1wartość . Zmienna expectedMessages reprezentuje ten oczekiwany wynik.

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

Metoda działa: DeleteMessageAsync metoda jest wykonywana przekazując w elemecie recId elementu 1:

// Act
await db.DeleteMessageAsync(recId);

Na koniec metoda uzyskuje Messages element z kontekstu i porównuje go z expectedMessages twierdzeniem, że te dwie są równe:

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

Aby porównać te dwa elementy List<Message> są takie same:

  • Komunikaty są uporządkowane według Id.
  • Pary komunikatów są porównywane z właściwością Text .

Podobna metoda DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound testowa sprawdza wynik próby usunięcia komunikatu, który nie istnieje. W takim przypadku oczekiwane komunikaty w bazie danych powinny być równe rzeczywistym komunikatom po wykonaniu DeleteMessageAsync metody. Nie należy zmieniać zawartości bazy danych:

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

Testy jednostkowe metod modelu strony

Inny zestaw testów jednostkowych jest odpowiedzialny za testy metod modelu strony. W aplikacji komunikatów modele strony Indeks znajdują się w klasie w pliku IndexModelsrc/RazorPagesTestSample/Pages/Index.cshtml.cs.

Metoda modelu strony Function
OnGetAsync Uzyskuje komunikaty z dal dla interfejsu użytkownika przy użyciu GetMessagesAsync metody .
OnPostAddMessageAsync Jeśli parametr ModelState jest prawidłowy, wywołania AddMessageAsync w celu dodania komunikatu do bazy danych.
OnPostDeleteAllMessagesAsync Wywołuje metodę DeleteAllMessagesAsync usuwania wszystkich komunikatów w bazie danych.
OnPostDeleteMessageAsync DeleteMessageAsync Wykonuje polecenie , aby usunąć komunikat z określoną wartościąId.
OnPostAnalyzeMessagesAsync Jeśli co najmniej jeden komunikat znajduje się w bazie danych, oblicza średnią liczbę wyrazów na komunikat.

Metody modelu strony są testowane przy użyciu siedmiu testów w IndexPageTests klasie (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Testy używają znanego wzorca Arrange-Assert-Act. Te testy koncentrują się na:

  • Określenie, czy metody są zgodne z prawidłowym zachowaniem, gdy element ModelState jest nieprawidłowy.
  • Potwierdzenie metod powoduje wygenerowanie poprawnej IActionResultmetody .
  • Sprawdzanie, czy przypisania wartości właściwości są poprawnie wykonywane.

Ta grupa testów często wyśmiewa metody dal w celu wygenerowania oczekiwanych danych dla kroku Act, w którym jest wykonywana metoda modelu strony. Na przykład GetMessagesAsync metoda AppDbContext metody jest wyśmiewany w celu wygenerowania danych wyjściowych. Gdy metoda modelu strony wykonuje tę metodę, pozorowanie zwraca wynik. Dane nie pochodzą z bazy danych. Spowoduje to utworzenie przewidywalnych, niezawodnych warunków testowych na potrzeby korzystania z dal w testach modelu strony.

Test OnGetAsync_PopulatesThePageModel_WithAListOfMessages pokazuje, jak GetMessagesAsync metoda jest wyśmiewany dla modelu strony:

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

OnGetAsync Gdy metoda jest wykonywana w kroku Act, wywołuje metodę modelu GetMessagesAsync strony.

Krok działania testu jednostkowego (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

IndexPage metoda modelu OnGetAsync strony (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Metoda GetMessagesAsync w dal nie zwraca wyniku dla tego wywołania metody. Wyśmiewany wersja metody zwraca wynik.

Assert W kroku rzeczywiste komunikaty (actualMessages) są przypisywane z Messages właściwości modelu strony. Sprawdzanie typu jest również wykonywane po przypisaniu komunikatów. Oczekiwane i rzeczywiste komunikaty są porównywane przez ich Text właściwości. Test potwierdza, że dwa List<Message> wystąpienia zawierają te same komunikaty.

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

Inne testy w tej grupie tworzą obiekty modelu strony, które obejmują DefaultHttpContext, ModelStateDictionary, an ActionContext do ustanowienia PageContext, , ViewDataDictionaryi PageContext. Są one przydatne podczas przeprowadzania testów. Na przykład aplikacja komunikatu ModelState ustanawia błąd, AddModelError aby sprawdzić, czy podczas wykonywania jest zwracany OnPostAddMessageAsync prawidłowy PageResult element:

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

Dodatkowe zasoby

ASP.NET Core obsługuje testy jednostkowe Razor aplikacji Pages. Testy warstwy dostępu do danych (DAL) i modeli stron pomagają zapewnić:

  • Razor Części aplikacji Pages działają niezależnie i razem jako jednostka podczas budowy aplikacji.
  • Klasy i metody mają ograniczone zakresy odpowiedzialności.
  • Dodatkowa dokumentacja istnieje na temat sposobu działania aplikacji.
  • Regresje, które są błędami spowodowanymi aktualizacjami kodu, są znajdowane podczas zautomatyzowanego kompilowania i wdrażania.

W tym temacie założono, że masz podstawową wiedzę na temat Razor aplikacji Pages i testów jednostkowych. Jeśli nie Razor znasz aplikacji Stron lub pojęć dotyczących testowania, zobacz następujące tematy:

Wyświetl lub pobierz przykładowy kod (jak pobrać)

Przykładowy projekt składa się z dwóch aplikacji:

App Folder projektu opis
Aplikacja komunikatów src/RazorPagesTestSample Umożliwia użytkownikowi dodawanie komunikatu, usuwanie jednej wiadomości, usuwanie wszystkich komunikatów i analizowanie komunikatów (znajdowanie średniej liczby słów na wiadomość).
Testowanie aplikacji tests/RazorPagesTestSample.Tests Służy do testowania jednostkowego modelu strony DAL i indeksu aplikacji komunikatów.

Testy można uruchamiać przy użyciu wbudowanych funkcji testowych środowiska IDE, takich jak Visual Studio. Jeśli używasz programu Visual Studio Code lub wiersza polecenia, wykonaj następujące polecenie w wierszu polecenia w folderze tests/RazorPagesTestSample.Tests :

dotnet test

Organizacja aplikacji komunikatów

Aplikacja komunikatów to Razor system komunikatów Pages o następujących cechach:

  • Strona Indeks aplikacji (Pages/Index.cshtml i Pages/Index.cshtml.cs) udostępnia metody interfejsu użytkownika i modelu strony do kontrolowania dodawania, usuwania i analizowania komunikatów (znajdowanie średniej liczby słów na komunikat).
  • Komunikat jest opisany przez klasę Message (Data/Message.cs) z dwiema właściwościami: Id (klucz) i Text (komunikat). Właściwość jest wymagana Text i ograniczona do 200 znaków.
  • Komunikaty są przechowywane przy użyciu bazy danych w pamięci programu Entity Framework†.
  • Aplikacja zawiera dal w swojej klasie AppDbContext kontekstu bazy danych (Data/AppDbContext.cs). Metody DAL są oznaczone jako virtual, co umożliwia wyśmiewanie metod do użycia w testach.
  • Jeśli baza danych jest pusta podczas uruchamiania aplikacji, magazyn komunikatów jest inicjowany z trzema komunikatami. Te komunikaty rozmieszczane są również używane w testach.

† Temat ef, Test with InMemory, wyjaśnia, jak używać bazy danych w pamięci do testów przy użyciu narzędzia MSTest. W tym temacie jest używana struktura testowa xUnit . Koncepcje testów i implementacje testów w różnych platformach testowych są podobne, ale nie identyczne.

Mimo że przykładowa aplikacja nie używa wzorca repozytorium i nie jest skutecznym przykładem wzorca Unit of Work (UoW), Razor strony obsługują te wzorce programowania. Aby uzyskać więcej informacji, zobacz Projektowanie warstwy trwałości infrastruktury i logiki kontrolera testów w programie ASP.NET Core (przykład implementuje wzorzec repozytorium).

Testowanie organizacji aplikacji

Aplikacja testowa jest aplikacją konsolową w folderze tests/RazorPagesTestSample.Tests .

Folder aplikacji testowej opis
UnitTests
  • DataAccessLayerTest.cs zawiera testy jednostkowe dal.
  • IndexPageTests.cs zawiera testy jednostkowe modelu strony indeksowania.
Narzędzia Zawiera metodę TestDbContextOptions używaną do tworzenia nowych opcji kontekstu bazy danych dla każdego testu jednostkowego DAL, dzięki czemu baza danych zostanie zresetowana do stanu odniesienia dla każdego testu.

Struktura testowa to xUnit. Struktura pozorowania obiektów to Moq.

Testy jednostkowe warstwy dostępu do danych (DAL)

Aplikacja komunikatów ma dal z czterema metodami zawartymi AppDbContext w klasie (src/RazorPagesTestSample/Data/AppDbContext.cs). Każda metoda ma jeden lub dwa testy jednostkowe w aplikacji testowej.

DAL, metoda Function
GetMessagesAsync Uzyskuje element List<Message> z bazy danych posortowany według Text właściwości .
AddMessageAsync Dodaje element Message do bazy danych.
DeleteAllMessagesAsync Usuwa wszystkie Message wpisy z bazy danych.
DeleteMessageAsync Usuwa pojedynczy element Message z bazy danych za pomocą polecenia Id.

Testy jednostkowe dal wymagają DbContextOptions utworzenia nowego AppDbContext dla każdego testu. Jednym z podejść do tworzenia DbContextOptions elementu dla każdego testu jest użycie elementu DbContextOptionsBuilder:

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

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

Problem z tym podejściem polega na tym, że każdy test odbiera bazę danych w każdym stanie, w jakim opuścił poprzedni test. Może to być problematyczne podczas próby zapisania niepodzielnych testów jednostkowych, które nie zakłócają siebie nawzajem. Aby wymusić AppDbContext użycie nowego kontekstu bazy danych dla każdego testu, podaj DbContextOptions wystąpienie oparte na nowym dostawcy usług. Aplikacja testowa pokazuje, jak to zrobić przy użyciu metody UtilitiesTestDbContextOptions klasy (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;
}

Użycie w DbContextOptions testach jednostkowych DAL umożliwia wykonywanie każdego testu niepodziealnie przy użyciu nowego wystąpienia bazy danych:

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

Każda metoda testowa w DataAccessLayerTest klasie (UnitTests/DataAccessLayerTest.cs) jest zgodna z podobnym wzorcem Arrange-Act-Assert:

  1. Rozmieść: baza danych jest skonfigurowana dla testu i/lub oczekiwany wynik jest definiowany.
  2. Act: Test jest wykonywany.
  3. Asercji: asercji są tworzone w celu określenia, czy wynik testu jest sukcesem.

Na przykład DeleteMessageAsync metoda jest odpowiedzialna za usunięcie pojedynczego komunikatu zidentyfikowanego przez element 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();
    }
}

Istnieją dwa testy dla tej metody. Jeden test sprawdza, czy metoda usuwa komunikat, gdy komunikat znajduje się w bazie danych. Inna metoda sprawdza, czy baza danych nie zmienia się, jeśli komunikat Id o usunięciu nie istnieje. Poniżej DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound przedstawiono metodę:

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

Najpierw metoda wykonuje krok Rozmieść, w którym odbywa się przygotowanie do wykonania kroku aktu. Komunikaty rozmieszczania są uzyskiwane i przechowywane w elemencie seedMessages. Komunikaty rozmieszczania są zapisywane w bazie danych. Komunikat z elementem Id1 jest ustawiony do usunięcia. Po wykonaniu DeleteMessageAsync metody oczekiwane komunikaty powinny mieć wszystkie komunikaty z wyjątkiem tego, który ma Id1wartość . Zmienna expectedMessages reprezentuje ten oczekiwany wynik.

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

Metoda działa: DeleteMessageAsync metoda jest wykonywana przekazując w elemecie recId elementu 1:

// Act
await db.DeleteMessageAsync(recId);

Na koniec metoda uzyskuje Messages element z kontekstu i porównuje go z expectedMessages twierdzeniem, że te dwie są równe:

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

Aby porównać te dwa elementy List<Message> są takie same:

  • Komunikaty są uporządkowane według Id.
  • Pary komunikatów są porównywane z właściwością Text .

Podobna metoda DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound testowa sprawdza wynik próby usunięcia komunikatu, który nie istnieje. W takim przypadku oczekiwane komunikaty w bazie danych powinny być równe rzeczywistym komunikatom po wykonaniu DeleteMessageAsync metody. Nie należy zmieniać zawartości bazy danych:

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

Testy jednostkowe metod modelu strony

Inny zestaw testów jednostkowych jest odpowiedzialny za testy metod modelu strony. W aplikacji komunikatów modele strony Indeks znajdują się w klasie w pliku IndexModelsrc/RazorPagesTestSample/Pages/Index.cshtml.cs.

Metoda modelu strony Function
OnGetAsync Uzyskuje komunikaty z dal dla interfejsu użytkownika przy użyciu GetMessagesAsync metody .
OnPostAddMessageAsync Jeśli parametr ModelState jest prawidłowy, wywołania AddMessageAsync w celu dodania komunikatu do bazy danych.
OnPostDeleteAllMessagesAsync Wywołuje metodę DeleteAllMessagesAsync usuwania wszystkich komunikatów w bazie danych.
OnPostDeleteMessageAsync DeleteMessageAsync Wykonuje polecenie , aby usunąć komunikat z określoną wartościąId.
OnPostAnalyzeMessagesAsync Jeśli co najmniej jeden komunikat znajduje się w bazie danych, oblicza średnią liczbę wyrazów na komunikat.

Metody modelu strony są testowane przy użyciu siedmiu testów w IndexPageTests klasie (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs). Testy używają znanego wzorca Arrange-Assert-Act. Te testy koncentrują się na:

  • Określenie, czy metody są zgodne z prawidłowym zachowaniem, gdy element ModelState jest nieprawidłowy.
  • Potwierdzenie metod powoduje wygenerowanie poprawnej IActionResultmetody .
  • Sprawdzanie, czy przypisania wartości właściwości są poprawnie wykonywane.

Ta grupa testów często wyśmiewa metody dal w celu wygenerowania oczekiwanych danych dla kroku Act, w którym jest wykonywana metoda modelu strony. Na przykład GetMessagesAsync metoda AppDbContext metody jest wyśmiewany w celu wygenerowania danych wyjściowych. Gdy metoda modelu strony wykonuje tę metodę, pozorowanie zwraca wynik. Dane nie pochodzą z bazy danych. Spowoduje to utworzenie przewidywalnych, niezawodnych warunków testowych na potrzeby korzystania z dal w testach modelu strony.

Test OnGetAsync_PopulatesThePageModel_WithAListOfMessages pokazuje, jak GetMessagesAsync metoda jest wyśmiewany dla modelu strony:

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

OnGetAsync Gdy metoda jest wykonywana w kroku Act, wywołuje metodę modelu GetMessagesAsync strony.

Krok działania testu jednostkowego (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs):

// Act
await pageModel.OnGetAsync();

IndexPage metoda modelu OnGetAsync strony (src/RazorPagesTestSample/Pages/Index.cshtml.cs):

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

Metoda GetMessagesAsync w dal nie zwraca wyniku dla tego wywołania metody. Wyśmiewany wersja metody zwraca wynik.

Assert W kroku rzeczywiste komunikaty (actualMessages) są przypisywane z Messages właściwości modelu strony. Sprawdzanie typu jest również wykonywane po przypisaniu komunikatów. Oczekiwane i rzeczywiste komunikaty są porównywane przez ich Text właściwości. Test potwierdza, że dwa List<Message> wystąpienia zawierają te same komunikaty.

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

Inne testy w tej grupie tworzą obiekty modelu strony, które obejmują DefaultHttpContext, ModelStateDictionary, an ActionContext do ustanowienia PageContext, , ViewDataDictionaryi PageContext. Są one przydatne podczas przeprowadzania testów. Na przykład aplikacja komunikatu ModelState ustanawia błąd, AddModelError aby sprawdzić, czy podczas wykonywania jest zwracany OnPostAddMessageAsync prawidłowy PageResult element:

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

Dodatkowe zasoby