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:
- Wprowadzenie do Razor stron w ASP.NET Core
- Samouczek: rozpoczynanie pracy z rozwiązaniem Razor Pages na platformie ASP.NET Core
- Testowanie jednostkowe języka C# na platformie .NET Core przy użyciu testu dotnet i narzędzia xUnit
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykładowy projekt składa się z dwóch aplikacji:
Aplikacja | 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
iPages/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) iText
(komunikat). Właściwość jest wymaganaText
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 jakovirtual
, 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ą wewnątrz folderu tests/RazorPagesTestSample.Tests .
Folder aplikacji testowej | opis |
---|---|
UnitTests |
|
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 Utilities
TestDbContextOptions
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:
- Rozmieść: baza danych jest skonfigurowana dla testu i/lub oczekiwany wynik jest definiowany.
- Act: Test jest wykonywany.
- 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 Id
1
jest ustawiony do usunięcia. Po wykonaniu DeleteMessageAsync
metody oczekiwane komunikaty powinny mieć wszystkie komunikaty z wyjątkiem tego, który ma Id
1
wartość . 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 IndexModel
src/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
, , ViewDataDictionary
i 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
- Testowanie jednostkowe języka C# na platformie .NET Core przy użyciu testu dotnet i narzędzia xUnit
- Logika kontrolera testów w ASP.NET Core
- Testowanie jednostkowe kodu (Visual Studio)
- Testy integracji w programie ASP.NET Core
- xUnit.net
- Wprowadzenie do xUnit.net: korzystanie z platformy .NET Core z wierszem polecenia zestawu .NET SDK
- Moq
- Przewodnik Szybki start dotyczący aplikacji Moq
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:
- Wprowadzenie do Razor stron w ASP.NET Core
- Samouczek: rozpoczynanie pracy z rozwiązaniem Razor Pages na platformie ASP.NET Core
- Testowanie jednostkowe języka C# na platformie .NET Core przy użyciu testu dotnet i narzędzia xUnit
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykładowy projekt składa się z dwóch aplikacji:
Aplikacja | 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
iPages/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) iText
(komunikat). Właściwość jest wymaganaText
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 jakovirtual
, 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ą wewnątrz folderu tests/RazorPagesTestSample.Tests .
Folder aplikacji testowej | opis |
---|---|
UnitTests |
|
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 Utilities
TestDbContextOptions
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:
- Rozmieść: baza danych jest skonfigurowana dla testu i/lub oczekiwany wynik jest definiowany.
- Act: Test jest wykonywany.
- 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 Id
1
jest ustawiony do usunięcia. Po wykonaniu DeleteMessageAsync
metody oczekiwane komunikaty powinny mieć wszystkie komunikaty z wyjątkiem tego, który ma Id
1
wartość . 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 IndexModel
src/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
, , ViewDataDictionary
i 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
- Testowanie jednostkowe języka C# na platformie .NET Core przy użyciu testu dotnet i narzędzia xUnit
- Logika kontrolera testów w ASP.NET Core
- Testowanie jednostkowe kodu (Visual Studio)
- Testy integracji w programie ASP.NET Core
- xUnit.net
- Wprowadzenie do xUnit.net: korzystanie z platformy .NET Core z wierszem polecenia zestawu .NET SDK
- Moq
- Przewodnik Szybki start dotyczący aplikacji Moq
- JustMockLite: pozorna struktura dla deweloperów platformy .NET. (Nieobsługiwane lub obsługiwane przez firmę Microsoft).