Testy integracji w programie ASP.NET Core
Przez Jos van der Til, Martin Costello i Javier Calvarro Nelson.
Testy integracji zapewniają prawidłowe działanie składników aplikacji na poziomie obejmującym infrastrukturę pomocniczą aplikacji, taką jak baza danych, system plików i sieć. ASP.NET Core obsługuje testy integracji przy użyciu struktury testów jednostkowych z testowym hostem internetowym i serwerem testowym w pamięci.
W tym artykule przyjęto założenie, że podstawowa wiedza na temat testów jednostkowych. Jeśli nie masz pewności co do pojęć związanych z testowaniem, zobacz artykuł Unit Testing in .NET Core and .NET Standard (Testowanie jednostkowe na platformie .NET Core i .NET Standard ) oraz jego połączoną zawartość.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykładowa aplikacja to Razor aplikacja Pages i przyjmuje podstawową wiedzę na temat Razor stron. Jeśli nie Razor znasz stron, zobacz następujące artykuły:
Do testowania spAs zalecamy narzędzie, takie jak Playwright dla platformy .NET, które może zautomatyzować przeglądarkę.
Wprowadzenie do testów integracji
Testy integracji oceniają składniki aplikacji na szerszym poziomie niż testy jednostkowe. Testy jednostkowe służą do testowania izolowanych składników oprogramowania, takich jak poszczególne metody klasy. Testy integracji potwierdzają, że co najmniej dwa składniki aplikacji współpracują ze sobą, aby wygenerować oczekiwany wynik, prawdopodobnie włącznie z każdym składnikiem wymaganym do pełnego przetworzenia żądania.
Te szersze testy służą do testowania infrastruktury aplikacji i całej struktury, często w tym następujących składników:
- baza danych
- System plików
- Urządzenia sieciowe
- Potok żądania odpowiedzi
Testy jednostkowe używają sfabrykowanych składników, znanych jako fałszywe lub makiety obiektów, zamiast składników infrastruktury.
W przeciwieństwie do testów jednostkowych, testy integracji:
- Użyj rzeczywistych składników używanych przez aplikację w środowisku produkcyjnym.
- Wymagaj więcej kodu i przetwarzania danych.
- Uruchamianie trwa dłużej.
W związku z tym ogranicz użycie testów integracji do najważniejszych scenariuszy infrastruktury. Jeśli zachowanie można przetestować przy użyciu testu jednostkowego lub testu integracji, wybierz test jednostkowy.
W dyskusjach na temat testów integracji testowany projekt jest często nazywany systemowym testem lub "SUT" w skrócie. Element "SUT" jest używany w tym artykule do odwoływania się do testowanej aplikacji ASP.NET Core.
Nie zapisuj testów integracji dla każdej permutacji danych i dostępu do plików za pomocą baz danych i systemów plików. Niezależnie od liczby miejsc w aplikacji współdziała z bazami danych i systemami plików, skoncentrowany zestaw testów integracji odczytu, zapisu, aktualizacji i usuwania zwykle umożliwia odpowiednie testowanie składników bazy danych i systemu plików. Używaj testów jednostkowych do rutynowych testów logiki metod, które wchodzą w interakcje z tymi składnikami. W testach jednostkowych użycie fałszywych lub pozorów infrastruktury skutkuje szybszym wykonaniem testu.
testy integracji ASP.NET Core
Testy integracji w programie ASP.NET Core wymagają następujących elementów:
- Projekt testowy służy do zawierania i wykonywania testów. Projekt testowy zawiera odwołanie do sutu.
- Projekt testowy tworzy testowego hosta internetowego dla sut i używa klienta serwera testowego do obsługi żądań i odpowiedzi przy użyciu sut.
- Moduł uruchamiający testy służy do wykonywania testów i zgłaszania wyników testu.
Testy integracji są zgodne z sekwencją zdarzeń obejmujących zwykłe kroki testu Rozmieszczanie, Działanie i Asercja :
- Host internetowy SUT jest skonfigurowany.
- Klient serwera testowego jest tworzony w celu przesyłania żądań do aplikacji.
- Krok Rozmieść test jest wykonywany: aplikacja testowa przygotowuje żądanie.
- Krok testu aktu jest wykonywany: klient przesyła żądanie i odbiera odpowiedź.
- Krok testu potwierdzenia jest wykonywany: rzeczywista odpowiedź jest weryfikowana jako powodzenie lub niepowodzenie w oparciu o oczekiwaną odpowiedź.
- Proces będzie kontynuowany do momentu wykonania wszystkich testów.
- Wyniki testów są zgłaszane.
Zazwyczaj testowy host internetowy jest skonfigurowany inaczej niż normalny host internetowy aplikacji na potrzeby przebiegów testu. Na przykład do testów mogą być używane różne ustawienia bazy danych lub różnych aplikacji.
Składniki infrastruktury, takie jak testowy host internetowy i serwer testowy w pamięci (TestServer), są dostarczane lub zarządzane przez pakiet Microsoft.AspNetCore.Mvc.Testing . Użycie tego pakietu usprawnia tworzenie i wykonywanie testów.
Pakiet Microsoft.AspNetCore.Mvc.Testing
obsługuje następujące zadania:
- Kopiuje plik zależności (
.deps
) z sutu do katalogu projektu testowegobin
. - Ustawia katalog główny zawartości na katalog główny projektu SUT, tak aby pliki statyczne i strony/widoki zostały znalezione podczas wykonywania testów.
- Udostępnia klasę WebApplicationFactory , aby usprawnić uruchamianie aplikacji SUT za pomocą polecenia
TestServer
.
W dokumentacji testów jednostkowych opisano sposób konfigurowania projektu testowego i modułu uruchamiającego testy oraz szczegółowe instrukcje dotyczące uruchamiania testów i zaleceń dotyczących sposobu nazywania testów i klas testowych.
Oddziel testy jednostkowe od testów integracji do różnych projektów. Oddzielanie testów:
- Pomaga zagwarantować, że składniki testowania infrastruktury nie zostaną przypadkowo uwzględnione w testach jednostkowych.
- Umożliwia kontrolę nad tym, który zestaw testów jest uruchamiany.
Praktycznie nie ma różnicy między konfiguracją testów Razor aplikacji Pages i aplikacji MVC. Jedyną różnicą jest sposób, w jaki testy są nazwane. Razor W aplikacji Pages testy punktów końcowych strony są zwykle nazwane po klasie modelu strony (na przykład IndexPageTests
w celu przetestowania integracji składników dla strony Indeks). W aplikacji MVC testy są zwykle zorganizowane według klas kontrolerów i nazwane po testowych kontrolerach (na przykład HomeControllerTests
do testowania integracji składników dla Home kontrolera).
Testowanie wymagań wstępnych aplikacji
Projekt testowy musi:
- Utwórz odwołanie do pakietu
Microsoft.AspNetCore.Mvc.Testing
. - Określ zestaw Web SDK w pliku projektu (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Te wymagania wstępne można zobaczyć w przykładowej aplikacji. tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
Sprawdź plik. Przykładowa aplikacja używa struktury testowej xUnit i biblioteki analizatora AngleSharp , więc przykładowa aplikacja również odwołuje się do:
W aplikacjach korzystających z xunit.runner.visualstudio
wersji 2.4.2 lub nowszej Microsoft.NET.Test.Sdk
projekt testowy musi odwoływać się do pakietu.
Program Entity Framework Core jest również używany w testach. Zobacz plik projektu w usłudze GitHub.
Środowisko SUT
Jeśli środowisko SUT nie jest ustawione, środowisko jest domyślnie ustawione na Programowanie.
Podstawowe testy z domyślną funkcją WebApplicationFactory
Uwidaczniaj niejawnie zdefiniowaną Program
klasę w projekcie testowym, wykonując jedną z następujących czynności:
Uwidaczniaj typy wewnętrzne z aplikacji internetowej do projektu testowego. Można to zrobić w pliku projektu SUT (
.csproj
):<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>
Ustaw klasę
Program
jako publiczną przy użyciu deklaracji klasy częściowej:var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }
Przykładowa aplikacja używa
Program
metody klasy częściowej.
WebApplicationFactory<TEntryPoint> służy do tworzenia elementu TestServer dla testów integracji. TEntryPoint
jest klasą punktu wejścia sut, zwykle Program.cs
.
Klasy testowe implementują interfejs oprawy klasy (IClassFixture
), aby wskazać, że klasa zawiera testy i udostępnia wystąpienia obiektów udostępnionych w testach w klasie.
Następująca klasa BasicTests
testowa , używa WebApplicationFactory
metody , aby uruchomić sut i udostępnić metodę HttpClient testową . Get_EndpointsReturnSuccessAndCorrectContentType
Metoda sprawdza, czy kod stanu odpowiedzi zakończył się pomyślnie (200–299), a Content-Type
nagłówek znajduje się text/html; charset=utf-8
na kilku stronach aplikacji.
CreateClient() Program tworzy wystąpienie HttpClient
, które automatycznie śledzi przekierowania i obsługuje pliki cookie.
public class BasicTests
: IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public BasicTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Domyślnie pliki cookie inne niż podstawowe nie są zachowywane w żądaniach, gdy zasady zgody ogólnego rozporządzenia o ochronie danych są włączone. Aby zachować nieistniene pliki cookie, takie jak te używane przez dostawcę TempData, oznacz je jako niezbędne w testach. Aby uzyskać instrukcje dotyczące oznaczania cookie elementu jako niezbędnego, zobacz Podstawowe pliki cookie.
AngleSharp a Application Parts
kontrole antyforgery
W tym artykule użyto analizatora AngleSharp do obsługi testów antyforgeryjnych przez ładowanie stron i analizowanie kodu HTML. Aby przetestować punkty końcowe widoków kontrolera i Razor stron na niższym poziomie, bez dbania o sposób renderowania w przeglądarce, rozważ użycie polecenia Application Parts
. Metoda Części aplikacji wprowadza kontroler lub Razor stronę do aplikacji, która może służyć do tworzenia żądań JSON w celu uzyskania wymaganych wartości. Aby uzyskać więcej informacji, zobacz blog Integration Testing ASP.NET Core Resources Protected with Antiforgery Using Application Parts and associated GitHub repo by Martin Costello (Testowanie integracji ASP.NET core resources protected with Antiforgery Using Application Parts and associated GitHub repo by Martin Costello).
Dostosowywanie elementu WebApplicationFactory
Konfigurację hosta internetowego można utworzyć niezależnie od klas testowych, dziedzicząc z WebApplicationFactory<TEntryPoint> w celu utworzenia co najmniej jednej fabryki niestandardowej:
Dziedzicz z
WebApplicationFactory
i przesłaniaj ConfigureWebHost. Element IWebHostBuilder umożliwia konfigurację kolekcji usług za pomocą poleceniaIWebHostBuilder.ConfigureServices
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { var dbContextDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)); services.Remove(dbContextDescriptor); var dbConnectionDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbConnection)); services.Remove(dbConnectionDescriptor); // Create open SqliteConnection so EF won't automatically close it. services.AddSingleton<DbConnection>(container => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); return connection; }); services.AddDbContext<ApplicationDbContext>((container, options) => { var connection = container.GetRequiredService<DbConnection>(); options.UseSqlite(connection); }); }); builder.UseEnvironment("Development"); } }
Rozmieszczanie bazy danych w przykładowej aplikacji jest wykonywane przez metodę
InitializeDbForTests
. Metoda została opisana w sekcji Przykładowe testy integracji: Testowanie organizacji aplikacji.Kontekst bazy danych SUT jest zarejestrowany w pliku
Program.cs
. Wywołanie zwrotne aplikacjibuilder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacjiProgram.cs
. Aby użyć innej bazy danych do testów niż baza danych aplikacji, kontekst bazy danych aplikacji musi zostać zastąpiony w plikubuilder.ConfigureServices
.Przykładowa aplikacja znajduje deskryptor usługi dla kontekstu bazy danych i używa deskryptora do usunięcia rejestracji usługi. Następnie fabryka dodaje nowy
ApplicationDbContext
, który używa bazy danych w pamięci do testów.Aby nawiązać połączenie z inną bazą danych, zmień wartość
DbConnection
. Aby użyć testowej bazy danych programu SQL Server:- Odwołanie do
Microsoft.EntityFrameworkCore.SqlServer
pakietu NuGet w pliku projektu. - Wywołaj polecenie
UseInMemoryDatabase
.
- Odwołanie do
Użyj niestandardowego
CustomWebApplicationFactory
w klasach testowych. W poniższym przykładzie użyto fabrykiIndexPageTests
w klasie :public class IndexPageTests : IClassFixture<CustomWebApplicationFactory<Program>> { private readonly HttpClient _client; private readonly CustomWebApplicationFactory<Program> _factory; public IndexPageTests( CustomWebApplicationFactory<Program> factory) { _factory = factory; _client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); }
Klient przykładowej aplikacji jest skonfigurowany tak, aby zapobiec
HttpClient
następującym przekierowaniom. Jak wyjaśniono w dalszej części sekcji uwierzytelnianie mock, pozwala to testom sprawdzić wynik pierwszej odpowiedzi aplikacji. Pierwsza odpowiedź to przekierowanie w wielu z tych testów z nagłówkiemLocation
.Typowy test używa
HttpClient
metod pomocnika i do przetwarzania żądania i odpowiedzi:[Fact] public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot() { // Arrange var defaultPage = await _client.GetAsync("/"); var content = await HtmlHelpers.GetDocumentAsync(defaultPage); //Act var response = await _client.SendAsync( (IHtmlFormElement)content.QuerySelector("form[id='messages']"), (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']")); // Assert Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal("/", response.Headers.Location.OriginalString); }
Każde żądanie POST do SUT musi spełniać kontrolę antyforgeryjną, która jest automatycznie dokonana przez system ochrony danych aplikacji. Aby zorganizować żądanie POST testu, aplikacja testowa musi:
- Utwórz żądanie dla strony.
- Przeanalizuj token antyforgery i cookie żądaj weryfikacji z odpowiedzi.
- Utwórz żądanie POST z tokenem antyforgery i cookie zażądaj weryfikacji.
SendAsync
Metody rozszerzenia pomocnika (Helpers/HttpClientExtensions.cs
) i GetDocumentAsync
metoda pomocnika (Helpers/HtmlHelpers.cs
) w przykładowej aplikacji używają analizatora AngleSharp do obsługi sprawdzania antyforgery przy użyciu następujących metod:
GetDocumentAsync
: Odbiera element HttpResponseMessage i zwraca wartośćIHtmlDocument
.GetDocumentAsync
używa fabryki, która przygotowuje wirtualną odpowiedź na podstawie oryginalnegoHttpResponseMessage
. Aby uzyskać więcej informacji, zobacz dokumentację AngleSharp.SendAsync
metody rozszerzenia dla redagowaniaHttpClient
i HttpRequestMessage wywołania SendAsync(HttpRequestMessage) w celu przesłania żądań do SUT. Przeciążenia akceptowaniaSendAsync
formularza HTML (IHtmlFormElement
) i następujących elementów:- Przycisk Prześlij formularza (
IHtmlElement
) - Kolekcja wartości formularza (
IEnumerable<KeyValuePair<string, string>>
) - Przycisk Prześlij (
IHtmlElement
) i wartości formularza (IEnumerable<KeyValuePair<string, string>>
)
- Przycisk Prześlij formularza (
AngleSharp to biblioteka analizy innej firmy używana do celów demonstracyjnych w tym artykule i przykładowej aplikacji. Funkcja AngleSharp nie jest obsługiwana ani wymagana do testowania integracji aplikacji ASP.NET Core. Można użyć innych analizatorów, takich jak Pakiet Zwinności HTML (HAP). Innym podejściem jest napisanie kodu do obsługi tokenu weryfikacji żądania systemu ochrony przed fałszerzami i bezpośredniego zwalczania.cookie Aby uzyskać więcej informacji, zobacz AngleSharp vs for antiforgery checks in this article (Kontrola kątowa i Application Parts
antyforgeryjna w tym artykule).
Dostawca bazy danych EF-Core w pamięci może służyć do ograniczonego i podstawowego testowania, jednak dostawca SQLite jest zalecanym wyborem do testowania w pamięci.
Zobacz Rozszerzanie uruchamiania za pomocą filtrów uruchamiania, które pokazują, jak skonfigurować oprogramowanie pośredniczące przy użyciu programu IStartupFilter, co jest przydatne, gdy test wymaga niestandardowej usługi lub oprogramowania pośredniczącego.
Dostosowywanie klienta za pomocą programu WithWebHostBuilder
Jeśli wymagana jest dodatkowa konfiguracja w metodzie testowej, WithWebHostBuilder tworzy nową WebApplicationFactory
z elementem IWebHostBuilder , który jest jeszcze bardziej dostosowany przez konfigurację.
Przykładowe wywołania WithWebHostBuilder
kodu w celu zastąpienia skonfigurowanych usług wycinkami testowymi. Aby uzyskać więcej informacji i przykładowe użycie, zobacz Wstrzykiwanie usług makiety w tym artykule.
Metoda Post_DeleteMessageHandler_ReturnsRedirectToRoot
testowa przykładowej aplikacji demonstruje użycie metody WithWebHostBuilder
. Ten test wykonuje usuwanie rekordów w bazie danych, wyzwalając przesłanie formularza w sut.
Ponieważ inny test w IndexPageTests
klasie wykonuje operację, która usuwa wszystkie rekordy w bazie danych i może zostać uruchomiona przed Post_DeleteMessageHandler_ReturnsRedirectToRoot
metodą, baza danych jest ponownie przesyłana w tej metodzie testowej, aby upewnić się, że rekord jest obecny, aby sut usunąć. Wybranie pierwszego przycisku messages
usuwania formularza w sut jest symulowane w żądaniu do sut:
[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
// Arrange
using (var scope = _factory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
Utilities.ReinitializeDbForTests(db);
}
var defaultPage = await _client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await _client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("form[id='messages']")
.QuerySelector("div[class='panel-body']")
.QuerySelector("button"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Opcje klienta
Zobacz stronę zawierającą WebApplicationFactoryClientOptions ustawienia domyślne i dostępne opcje podczas tworzenia HttpClient
wystąpień.
Utwórz klasę WebApplicationFactoryClientOptions
i przekaż ją do CreateClient() metody :
public class IndexPageTests :
IClassFixture<CustomWebApplicationFactory<Program>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Program>
_factory;
public IndexPageTests(
CustomWebApplicationFactory<Program> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
UWAGA: Aby uniknąć ostrzeżeń przekierowania HTTPS w dziennikach podczas korzystania z oprogramowania pośredniczącego przekierowania HTTPS, ustaw BaseAddress = new Uri("https://localhost")
Wstrzykiwanie makiety usług
Usługi można zastąpić w teście za pomocą wywołania metody ConfigureTestServices w konstruktorze hosta. Aby ograniczyć zakres przesłonięć usług do samego testu, WithWebHostBuilder metoda jest używana do pobierania konstruktora hostów. Można to zobaczyć w następujących testach:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Przykładowy kod SUT zawiera usługę o określonym zakresie, która zwraca cudzysłów. Cudzysłów jest osadzony w ukrytym polu na stronie Indeks po zażądaniu strony Indeks.
Services/IQuoteService.cs
:
public interface IQuoteService
{
Task<string> GenerateQuote();
}
Services/QuoteService.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult<string>(
"Come on, Sarah. We've an appointment in London, " +
"and we're already 30,000 years late.");
}
}
Program.cs
:
services.AddScoped<IQuoteService, QuoteService>();
Pages/Index.cshtml.cs
:
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _db;
private readonly IQuoteService _quoteService;
public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
{
_db = db;
_quoteService = quoteService;
}
[BindProperty]
public Message Message { get; set; }
public IList<Message> Messages { get; private set; }
[TempData]
public string MessageAnalysisResult { get; set; }
public string Quote { get; private set; }
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
Quote = await _quoteService.GenerateQuote();
}
Pages/Index.cs
:
<input id="quote" type="hidden" value="@Model.Quote">
Po uruchomieniu aplikacji SUT jest generowany następujący znacznik:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Aby przetestować usługę i zacytować iniekcję w teście integracji, usługa pozorowana jest wstrzykiwana do suta przez test. Usługa pozorowania zastępuje aplikację QuoteService
usługą dostarczaną przez aplikację testową o nazwie TestQuoteService
:
IntegrationTests.IndexPageTests.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult(
"Something's interfering with time, Mr. Scarman, " +
"and time is my business.");
}
}
ConfigureTestServices
jest wywoływana, a usługa o określonym zakresie jest zarejestrowana:
[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddScoped<IQuoteService, TestQuoteService>();
});
})
.CreateClient();
//Act
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
var quoteElement = content.QuerySelector("#quote");
// Assert
Assert.Equal("Something's interfering with time, Mr. Scarman, " +
"and time is my business.", quoteElement.Attributes["value"].Value);
}
Znacznik wygenerowany podczas wykonywania testu odzwierciedla tekst cudzysłowu dostarczony przez TestQuoteService
element , w związku z czym asercji przechodzi:
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Pozorowanie uwierzytelniania
Testy w AuthTests
klasie sprawdzają, czy bezpieczny punkt końcowy:
- Przekierowuje nieuwierzytelnionego użytkownika do strony logowania aplikacji.
- Zwraca zawartość dla uwierzytelnionego użytkownika.
Na stronie SUT /SecurePage
jest używana AuthorizePage konwencja stosowania elementu AuthorizeFilter do strony. Aby uzyskać więcej informacji, zobacz Razor Konwencje autoryzacji stron.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
W teście Get_SecurePageRedirectsAnUnauthenticatedUser
parametr jest WebApplicationFactoryClientOptions ustawiony tak, aby nie zezwalał na przekierowania, ustawiając wartość AllowAutoRedirect na false
:
[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
// Arrange
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
// Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.StartsWith("http://localhost/Identity/Account/Login",
response.Headers.Location.OriginalString);
}
Nie zezwalając klientowi na obserwowanie przekierowania, można wykonać następujące kontrole:
- Kod stanu zwrócony przez sut można sprawdzić względem oczekiwanego HttpStatusCode.Redirect wyniku, a nie końcowy kod stanu po przekierowaniu do strony logowania, która byłaby .HttpStatusCode.OK
- Wartość nagłówka
Location
w nagłówkach odpowiedzi jest sprawdzana, aby potwierdzić, że rozpoczyna się odhttp://localhost/Identity/Account/Login
, a nie końcowej odpowiedzi strony logowania, w którejLocation
nagłówek nie będzie obecny.
Aplikacja testowa może wyśmiewać element AuthenticationHandler<TOptions> w ConfigureTestServices celu przetestowania aspektów uwierzytelniania i autoryzacji. Minimalny scenariusz zwraca wartość :AuthenticateResult.Success
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder)
: base(options, logger, encoder)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "TestScheme");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Element TestAuthHandler
jest wywoływany w celu uwierzytelnienia użytkownika, gdy schemat uwierzytelniania jest ustawiony na TestScheme
miejsce, w którym AddAuthentication
jest zarejestrowany dla ConfigureTestServices
programu . Ważne jest, TestScheme
aby schemat był zgodny ze schematem oczekiwanym przez aplikację. W przeciwnym razie uwierzytelnianie nie będzie działać.
[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(defaultScheme: "TestScheme")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"TestScheme", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(scheme: "TestScheme");
//Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Aby uzyskać więcej informacji na WebApplicationFactoryClientOptions
temat programu , zobacz sekcję Opcje klienta.
Podstawowe testy oprogramowania pośredniczącego uwierzytelniania
Zobacz to repozytorium GitHub, aby zapoznać się z podstawowymi testami oprogramowania pośredniczącego uwierzytelniania. Zawiera on serwer testowy specyficzny dla scenariusza testowego.
Ustawianie środowiska
Ustaw środowisko w niestandardowej fabryce aplikacji:
public class CustomWebApplicationFactory<TProgram>
: WebApplicationFactory<TProgram> where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var dbContextDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
services.Remove(dbContextDescriptor);
var dbConnectionDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbConnection));
services.Remove(dbConnectionDescriptor);
// Create open SqliteConnection so EF won't automatically close it.
services.AddSingleton<DbConnection>(container =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
services.AddDbContext<ApplicationDbContext>((container, options) =>
{
var connection = container.GetRequiredService<DbConnection>();
options.UseSqlite(connection);
});
});
builder.UseEnvironment("Development");
}
}
Jak infrastruktura testowa wywnioskuje ścieżkę główną zawartości aplikacji
Konstruktor WebApplicationFactory
wywnioskuje ścieżkę katalogu głównego zawartości aplikacji, wyszukując element WebApplicationFactoryContentRootAttribute w zestawie zawierającym testy integracji z kluczem równym zestawowi System.Reflection.Assembly.FullName
TEntryPoint
. Jeśli atrybut z poprawnym kluczem nie zostanie znaleziony, WebApplicationFactory
wróć do wyszukiwania pliku rozwiązania (.sln) i dołącza TEntryPoint
nazwę zestawu do katalogu rozwiązania. Katalog główny aplikacji (ścieżka główna zawartości) służy do odnajdywania widoków i plików zawartości.
Wyłączanie kopiowania w tle
Kopiowanie w tle powoduje wykonanie testów w innym katalogu niż katalog wyjściowy. Jeśli testy polegają na ładowaniu plików względem Assembly.Location
i napotkaniu problemów, może być konieczne wyłączenie kopiowania w tle.
Aby wyłączyć kopiowanie w tle podczas korzystania z narzędzia xUnit, utwórz xunit.runner.json
plik w katalogu projektu testowego z poprawnym ustawieniem konfiguracji:
{
"shadowCopy": false
}
Usuwanie obiektów
Po wykonaniu IClassFixture
TestServer testów implementacji i HttpClient usunięciu elementu xUnit z klasy WebApplicationFactory
. Jeśli obiekty utworzone przez dewelopera wymagają usunięcia, należy je usunąć w implementacji IClassFixture
. Aby uzyskać więcej informacji, zobacz Implementowanie metody Dispose.
Przykład testów integracji
Przykładowa aplikacja składa się z dwóch aplikacji:
Aplikacja | Katalog projektu | opis |
---|---|---|
Aplikacja komunikatów (SUT) | src/RazorPagesProject |
Umożliwia użytkownikowi dodawanie, usuwanie jednego, usuwanie wszystkich i analizowanie komunikatów. |
Testowanie aplikacji | tests/RazorPagesProject.Tests |
Służy do testowania integracji sut. |
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 tests/RazorPagesProject.Tests
katalogu:
dotnet test
Organizacja aplikacji komunikatów (SUT)
SUT 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 (średnie słowa 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 warstwę dostępu do danych (DAL) w swojej klasie
AppDbContext
kontekstu bazy danych (Data/AppDbContext.cs
). - Jeśli baza danych jest pusta podczas uruchamiania aplikacji, magazyn komunikatów jest inicjowany z trzema komunikatami.
- Aplikacja zawiera element
/SecurePage
, do którego można uzyskać dostęp tylko przez uwierzytelnionego użytkownika.
† Artykuł EF Test with InMemory (Testowanie za pomocą rozwiązania 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 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 Logika kontrolera testów (przykład implementuje wzorzec repozytorium).
Testowanie organizacji aplikacji
Aplikacja testowa jest aplikacją konsolową wewnątrz tests/RazorPagesProject.Tests
katalogu.
Testowanie katalogu aplikacji | opis |
---|---|
AuthTests |
Zawiera metody testowe dla:
|
BasicTests |
Zawiera metodę testową routingu i typu zawartości. |
IntegrationTests |
Zawiera testy integracji dla strony Indeks przy użyciu klasy niestandardowej WebApplicationFactory . |
Helpers/Utilities |
|
Struktura testowa to xUnit. Testy integracji są przeprowadzane przy użyciu elementu Microsoft.AspNetCore.TestHost, który zawiera TestServerelement . Microsoft.AspNetCore.Mvc.Testing
Ponieważ pakiet jest używany do konfigurowania hosta testowego i serwera testowego, TestHost
pakiety i TestServer
nie wymagają odwołań do bezpośredniego pakietu w pliku projektu aplikacji testowej ani konfiguracji dewelopera w aplikacji testowej.
Testy integracji zwykle wymagają małego zestawu danych w bazie danych przed wykonaniem testu. Na przykład test usuwania wywołuje usunięcie rekordu bazy danych, więc baza danych musi mieć co najmniej jeden rekord, aby żądanie usunięcia powiodło się.
Przykładowa aplikacja wyskakuje bazę danych z trzema komunikatami w Utilities.cs
tych testach, które mogą być używane podczas wykonywania:
public static void InitializeDbForTests(ApplicationDbContext db)
{
db.Messages.AddRange(GetSeedingMessages());
db.SaveChanges();
}
public static void ReinitializeDbForTests(ApplicationDbContext db)
{
db.Messages.RemoveRange(db.Messages);
InitializeDbForTests(db);
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
new Message(){ Text = "TEST RECORD: To the rational mind, " +
"nothing is inexplicable; only unexplained." }
};
}
Kontekst bazy danych SUT jest zarejestrowany w pliku Program.cs
. Wywołanie zwrotne aplikacji builder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacji Program.cs
. Aby użyć innej bazy danych do testów, kontekst bazy danych aplikacji musi zostać zastąpiony w pliku builder.ConfigureServices
. Aby uzyskać więcej informacji, zobacz sekcję Customize WebApplicationFactory (Dostosowywanie elementu WebApplicationFactory ).
Dodatkowe zasoby
W tym temacie przyjęto założenie, że podstawowa wiedza na temat testów jednostkowych. Jeśli nie masz pewności co do pojęć związanych z testowaniem, zobacz temat Unit Testing in .NET Core and .NET Standard (Testowanie jednostkowe na platformie .NET Core i .NET Standard ) oraz jego połączoną zawartość.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykładowa aplikacja to Razor aplikacja Pages i przyjmuje podstawową wiedzę na temat Razor stron. Jeśli nie masz pewności co do stron, zapoznaj się z Razor następującymi tematami:
Uwaga
Do testowania spAs zalecamy narzędzie, takie jak Playwright dla platformy .NET, które może zautomatyzować przeglądarkę.
Wprowadzenie do testów integracji
Testy integracji oceniają składniki aplikacji na szerszym poziomie niż testy jednostkowe. Testy jednostkowe służą do testowania izolowanych składników oprogramowania, takich jak poszczególne metody klasy. Testy integracji potwierdzają, że co najmniej dwa składniki aplikacji współpracują ze sobą, aby wygenerować oczekiwany wynik, prawdopodobnie włącznie z każdym składnikiem wymaganym do pełnego przetworzenia żądania.
Te szersze testy służą do testowania infrastruktury aplikacji i całej struktury, często w tym następujących składników:
- baza danych
- System plików
- Urządzenia sieciowe
- Potok żądania odpowiedzi
Testy jednostkowe używają sfabrykowanych składników, znanych jako fałszywe lub makiety obiektów, zamiast składników infrastruktury.
W przeciwieństwie do testów jednostkowych, testy integracji:
- Użyj rzeczywistych składników używanych przez aplikację w środowisku produkcyjnym.
- Wymagaj więcej kodu i przetwarzania danych.
- Uruchamianie trwa dłużej.
W związku z tym ogranicz użycie testów integracji do najważniejszych scenariuszy infrastruktury. Jeśli zachowanie można przetestować przy użyciu testu jednostkowego lub testu integracji, wybierz test jednostkowy.
W dyskusjach na temat testów integracji testowany projekt jest często nazywany systemowym testem lub "SUT" w skrócie. Element "SUT" jest używany w tym artykule do odwoływania się do testowanej aplikacji ASP.NET Core.
Nie zapisuj testów integracji dla każdej permutacji danych i dostępu do plików za pomocą baz danych i systemów plików. Niezależnie od liczby miejsc w aplikacji współdziała z bazami danych i systemami plików, skoncentrowany zestaw testów integracji odczytu, zapisu, aktualizacji i usuwania zwykle umożliwia odpowiednie testowanie składników bazy danych i systemu plików. Używaj testów jednostkowych do rutynowych testów logiki metod, które wchodzą w interakcje z tymi składnikami. W testach jednostkowych użycie fałszywych lub pozorów infrastruktury skutkuje szybszym wykonaniem testu.
testy integracji ASP.NET Core
Testy integracji w programie ASP.NET Core wymagają następujących elementów:
- Projekt testowy służy do zawierania i wykonywania testów. Projekt testowy zawiera odwołanie do sutu.
- Projekt testowy tworzy testowego hosta internetowego dla sut i używa klienta serwera testowego do obsługi żądań i odpowiedzi przy użyciu sut.
- Moduł uruchamiający testy służy do wykonywania testów i zgłaszania wyników testu.
Testy integracji są zgodne z sekwencją zdarzeń obejmujących zwykłe kroki testu Rozmieszczanie, Działanie i Asercja :
- Host internetowy SUT jest skonfigurowany.
- Klient serwera testowego jest tworzony w celu przesyłania żądań do aplikacji.
- Krok Rozmieść test jest wykonywany: aplikacja testowa przygotowuje żądanie.
- Krok testu aktu jest wykonywany: klient przesyła żądanie i odbiera odpowiedź.
- Krok testu potwierdzenia jest wykonywany: rzeczywista odpowiedź jest weryfikowana jako powodzenie lub niepowodzenie w oparciu o oczekiwaną odpowiedź.
- Proces będzie kontynuowany do momentu wykonania wszystkich testów.
- Wyniki testów są zgłaszane.
Zazwyczaj testowy host internetowy jest skonfigurowany inaczej niż normalny host internetowy aplikacji na potrzeby przebiegów testu. Na przykład do testów mogą być używane różne ustawienia bazy danych lub różnych aplikacji.
Składniki infrastruktury, takie jak testowy host internetowy i serwer testowy w pamięci (TestServer), są dostarczane lub zarządzane przez pakiet Microsoft.AspNetCore.Mvc.Testing . Użycie tego pakietu usprawnia tworzenie i wykonywanie testów.
Pakiet Microsoft.AspNetCore.Mvc.Testing
obsługuje następujące zadania:
- Kopiuje plik zależności (
.deps
) z sutu do katalogu projektu testowegobin
. - Ustawia katalog główny zawartości na katalog główny projektu SUT, tak aby pliki statyczne i strony/widoki zostały znalezione podczas wykonywania testów.
- Udostępnia klasę WebApplicationFactory , aby usprawnić uruchamianie aplikacji SUT za pomocą polecenia
TestServer
.
W dokumentacji testów jednostkowych opisano sposób konfigurowania projektu testowego i modułu uruchamiającego testy oraz szczegółowe instrukcje dotyczące uruchamiania testów i zaleceń dotyczących sposobu nazywania testów i klas testowych.
Oddziel testy jednostkowe od testów integracji do różnych projektów. Oddzielanie testów:
- Pomaga zagwarantować, że składniki testowania infrastruktury nie zostaną przypadkowo uwzględnione w testach jednostkowych.
- Umożliwia kontrolę nad tym, który zestaw testów jest uruchamiany.
Praktycznie nie ma różnicy między konfiguracją testów Razor aplikacji Pages i aplikacji MVC. Jedyną różnicą jest sposób, w jaki testy są nazwane. Razor W aplikacji Pages testy punktów końcowych strony są zwykle nazwane po klasie modelu strony (na przykład IndexPageTests
w celu przetestowania integracji składników dla strony Indeks). W aplikacji MVC testy są zwykle zorganizowane według klas kontrolerów i nazwane po testowych kontrolerach (na przykład HomeControllerTests
do testowania integracji składników dla Home kontrolera).
Testowanie wymagań wstępnych aplikacji
Projekt testowy musi:
- Utwórz odwołanie do pakietu
Microsoft.AspNetCore.Mvc.Testing
. - Określ zestaw Web SDK w pliku projektu (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Te wymagania wstępne można zobaczyć w przykładowej aplikacji. tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
Sprawdź plik. Przykładowa aplikacja używa struktury testowej xUnit i biblioteki analizatora AngleSharp , więc przykładowa aplikacja również odwołuje się do:
W aplikacjach korzystających z xunit.runner.visualstudio
wersji 2.4.2 lub nowszej Microsoft.NET.Test.Sdk
projekt testowy musi odwoływać się do pakietu.
Program Entity Framework Core jest również używany w testach. Odwołania do aplikacji:
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory
Microsoft.EntityFrameworkCore.Tools
Środowisko SUT
Jeśli środowisko SUT nie jest ustawione, środowisko jest domyślnie ustawione na Programowanie.
Podstawowe testy z domyślną funkcją WebApplicationFactory
WebApplicationFactory<TEntryPoint> służy do tworzenia elementu TestServer dla testów integracji. TEntryPoint
jest klasą punktu wejścia sut, zwykle klasą Startup
.
Klasy testowe implementują interfejs oprawy klasy (IClassFixture
), aby wskazać, że klasa zawiera testy i udostępnia wystąpienia obiektów udostępnionych w testach w klasie.
Następująca klasa BasicTests
testowa , używa WebApplicationFactory
metody , aby uruchomić sut i udostępnić metodę HttpClient testową . Get_EndpointsReturnSuccessAndCorrectContentType
Metoda sprawdza, czy kod stanu odpowiedzi zakończył się pomyślnie (kody stanu w zakresie od 200 do 299), a Content-Type
nagłówek dotyczy text/html; charset=utf-8
kilku stron aplikacji.
CreateClient() Program tworzy wystąpienie HttpClient
, które automatycznie śledzi przekierowania i obsługuje pliki cookie.
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly WebApplicationFactory<RazorPagesProject.Startup> _factory;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Domyślnie pliki cookie inne niż podstawowe nie są zachowywane w żądaniach, gdy zasady zgody RODO są włączone. Aby zachować nieistniene pliki cookie, takie jak te używane przez dostawcę TempData, oznacz je jako niezbędne w testach. Aby uzyskać instrukcje dotyczące oznaczania cookie elementu jako niezbędnego, zobacz Podstawowe pliki cookie.
Dostosowywanie elementu WebApplicationFactory
Konfigurację hosta internetowego można utworzyć niezależnie od klas testowych, dziedzicząc z WebApplicationFactory
w celu utworzenia co najmniej jednej fabryki niestandardowej:
Dziedzicz z
WebApplicationFactory
i przesłaniaj ConfigureWebHost. Element IWebHostBuilder umożliwia konfigurację kolekcji usług za pomocą polecenia ConfigureServices:public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { var descriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)); services.Remove(descriptor); services.AddDbContext<ApplicationDbContext>(options => { options.UseInMemoryDatabase("InMemoryDbForTesting"); }); var sp = services.BuildServiceProvider(); using (var scope = sp.CreateScope()) { var scopedServices = scope.ServiceProvider; var db = scopedServices.GetRequiredService<ApplicationDbContext>(); var logger = scopedServices .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>(); db.Database.EnsureCreated(); try { Utilities.InitializeDbForTests(db); } catch (Exception ex) { logger.LogError(ex, "An error occurred seeding the " + "database with test messages. Error: {Message}", ex.Message); } } }); } }
Rozmieszczanie bazy danych w przykładowej aplikacji jest wykonywane przez metodę
InitializeDbForTests
. Metoda została opisana w sekcji Przykładowe testy integracji: Testowanie organizacji aplikacji.Kontekst bazy danych SUT jest zarejestrowany w swojej
Startup.ConfigureServices
metodzie. Wywołanie zwrotne aplikacjibuilder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacjiStartup.ConfigureServices
. Kolejność wykonywania to zmiana powodująca niezgodność dla hosta ogólnego z wydaniem ASP.NET Core 3.0. Aby użyć innej bazy danych do testów niż baza danych aplikacji, kontekst bazy danych aplikacji musi zostać zastąpiony w plikubuilder.ConfigureServices
.W przypadku jednostek UWIERZYTELNIANIA, które nadal korzystają z hosta internetowego, wywołanie zwrotne aplikacji
builder.ConfigureServices
testowej jest wykonywane przed kodem SUTStartup.ConfigureServices
. Wywołanie zwrotne aplikacji testowejbuilder.ConfigureTestServices
jest wykonywane po.Przykładowa aplikacja znajduje deskryptor usługi dla kontekstu bazy danych i używa deskryptora do usunięcia rejestracji usługi. Następnie fabryka dodaje nową
ApplicationDbContext
bazę danych używającą bazy danych w pamięci do testów.Aby nawiązać połączenie z inną bazą danych niż baza danych w pamięci, zmień wywołanie
UseInMemoryDatabase
, aby połączyć kontekst z inną bazą danych. Aby użyć testowej bazy danych programu SQL Server:- Odwołanie do
Microsoft.EntityFrameworkCore.SqlServer
pakietu NuGet w pliku projektu. - Wywołaj metodę
UseSqlServer
przy użyciu parametry połączenia do bazy danych.
services.AddDbContext<ApplicationDbContext>((options, context) => { context.UseSqlServer( Configuration.GetConnectionString("TestingDbConnectionString")); });
- Odwołanie do
Użyj niestandardowego
CustomWebApplicationFactory
w klasach testowych. W poniższym przykładzie użyto fabrykiIndexPageTests
w klasie :public class IndexPageTests : IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>> { private readonly HttpClient _client; private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> _factory; public IndexPageTests( CustomWebApplicationFactory<RazorPagesProject.Startup> factory) { _factory = factory; _client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); }
Klient przykładowej aplikacji jest skonfigurowany tak, aby zapobiec
HttpClient
następującym przekierowaniom. Jak wyjaśniono w dalszej części sekcji uwierzytelnianie mock, pozwala to testom sprawdzić wynik pierwszej odpowiedzi aplikacji. Pierwsza odpowiedź to przekierowanie w wielu z tych testów z nagłówkiemLocation
.Typowy test używa
HttpClient
metod pomocnika i do przetwarzania żądania i odpowiedzi:[Fact] public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot() { // Arrange var defaultPage = await _client.GetAsync("/"); var content = await HtmlHelpers.GetDocumentAsync(defaultPage); //Act var response = await _client.SendAsync( (IHtmlFormElement)content.QuerySelector("form[id='messages']"), (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']")); // Assert Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal("/", response.Headers.Location.OriginalString); }
Każde żądanie POST do SUT musi spełniać kontrolę antyforgeryjną, która jest automatycznie dokonana przez system ochrony danych aplikacji. Aby zorganizować żądanie POST testu, aplikacja testowa musi:
- Utwórz żądanie dla strony.
- Przeanalizuj token antyforgery i cookie żądaj weryfikacji z odpowiedzi.
- Utwórz żądanie POST z tokenem antyforgery i cookie zażądaj weryfikacji.
SendAsync
Metody rozszerzenia pomocnika (Helpers/HttpClientExtensions.cs
) i GetDocumentAsync
metoda pomocnika (Helpers/HtmlHelpers.cs
) w przykładowej aplikacji używają analizatora AngleSharp do obsługi sprawdzania antyforgery przy użyciu następujących metod:
GetDocumentAsync
: Odbiera element HttpResponseMessage i zwraca wartośćIHtmlDocument
.GetDocumentAsync
używa fabryki, która przygotowuje wirtualną odpowiedź na podstawie oryginalnegoHttpResponseMessage
. Aby uzyskać więcej informacji, zobacz dokumentację AngleSharp.SendAsync
metody rozszerzenia dla redagowaniaHttpClient
i HttpRequestMessage wywołania SendAsync(HttpRequestMessage) w celu przesłania żądań do SUT. Przeciążenia akceptowaniaSendAsync
formularza HTML (IHtmlFormElement
) i następujących elementów:- Przycisk Prześlij formularza (
IHtmlElement
) - Kolekcja wartości formularza (
IEnumerable<KeyValuePair<string, string>>
) - Przycisk Prześlij (
IHtmlElement
) i wartości formularza (IEnumerable<KeyValuePair<string, string>>
)
- Przycisk Prześlij formularza (
Uwaga
AngleSharp to biblioteka analizy innej firmy używana do celów demonstracyjnych w tym temacie i przykładowej aplikacji. Funkcja AngleSharp nie jest obsługiwana ani wymagana do testowania integracji aplikacji ASP.NET Core. Można użyć innych analizatorów, takich jak Pakiet Zwinności HTML (HAP). Innym podejściem jest napisanie kodu do obsługi tokenu weryfikacji żądania systemu ochrony przed fałszerzami i bezpośredniego zwalczania.cookie
Uwaga
Dostawca bazy danych EF-Core w pamięci może służyć do ograniczonego i podstawowego testowania, jednak dostawca SQLite jest zalecanym wyborem do testowania w pamięci.
Dostosowywanie klienta za pomocą programu WithWebHostBuilder
Jeśli wymagana jest dodatkowa konfiguracja w metodzie testowej, WithWebHostBuilder tworzy nową WebApplicationFactory
z elementem IWebHostBuilder , który jest jeszcze bardziej dostosowany przez konfigurację.
Metoda Post_DeleteMessageHandler_ReturnsRedirectToRoot
testowa przykładowej aplikacji demonstruje użycie metody WithWebHostBuilder
. Ten test wykonuje usuwanie rekordów w bazie danych, wyzwalając przesłanie formularza w sut.
Ponieważ inny test w IndexPageTests
klasie wykonuje operację, która usuwa wszystkie rekordy w bazie danych i może zostać uruchomiona przed Post_DeleteMessageHandler_ReturnsRedirectToRoot
metodą, baza danych jest ponownie przesyłana w tej metodzie testowej, aby upewnić się, że rekord jest obecny, aby sut usunąć. Wybranie pierwszego przycisku messages
usuwania formularza w sut jest symulowane w żądaniu do sut:
[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
var serviceProvider = services.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices
.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<IndexPageTests>>();
try
{
Utilities.ReinitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred seeding " +
"the database with test messages. Error: {Message}",
ex.Message);
}
}
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("form[id='messages']")
.QuerySelector("div[class='panel-body']")
.QuerySelector("button"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Opcje klienta
W poniższej tabeli przedstawiono domyślną WebApplicationFactoryClientOptions dostępną podczas tworzenia HttpClient
wystąpień.
Opcja | Opis | Wartość domyślna |
---|---|---|
AllowAutoRedirect | Pobiera lub ustawia, czy HttpClient wystąpienia powinny automatycznie śledzić odpowiedzi przekierowania. |
true |
BaseAddress | Pobiera lub ustawia podstawowy adres HttpClient wystąpień. |
http://localhost |
HandleCookies | Pobiera lub ustawia, czy HttpClient wystąpienia powinny obsługiwać pliki cookie. |
true |
MaxAutomaticRedirections | Pobiera lub ustawia maksymalną liczbę odpowiedzi przekierowania, które HttpClient powinny być zgodne z wystąpieniami. |
7 |
Utwórz klasę WebApplicationFactoryClientOptions
i przekaż ją do CreateClient() metody (wartości domyślne są wyświetlane w przykładzie kodu):
// Default client option values are shown
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = true;
clientOptions.BaseAddress = new Uri("http://localhost");
clientOptions.HandleCookies = true;
clientOptions.MaxAutomaticRedirections = 7;
_client = _factory.CreateClient(clientOptions);
Wstrzykiwanie makiety usług
Usługi można zastąpić w teście za pomocą wywołania metody ConfigureTestServices w konstruktorze hosta. Aby wstrzyknąć pozorne usługi, sut musi mieć klasę Startup
Startup.ConfigureServices
z metodą .
Przykładowy kod SUT zawiera usługę o określonym zakresie, która zwraca cudzysłów. Cudzysłów jest osadzony w ukrytym polu na stronie Indeks po zażądaniu strony Indeks.
Services/IQuoteService.cs
:
public interface IQuoteService
{
Task<string> GenerateQuote();
}
Services/QuoteService.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult<string>(
"Come on, Sarah. We've an appointment in London, " +
"and we're already 30,000 years late.");
}
}
Startup.cs
:
services.AddScoped<IQuoteService, QuoteService>();
Pages/Index.cshtml.cs
:
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _db;
private readonly IQuoteService _quoteService;
public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
{
_db = db;
_quoteService = quoteService;
}
[BindProperty]
public Message Message { get; set; }
public IList<Message> Messages { get; private set; }
[TempData]
public string MessageAnalysisResult { get; set; }
public string Quote { get; private set; }
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
Quote = await _quoteService.GenerateQuote();
}
Pages/Index.cs
:
<input id="quote" type="hidden" value="@Model.Quote">
Po uruchomieniu aplikacji SUT jest generowany następujący znacznik:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Aby przetestować usługę i zacytować iniekcję w teście integracji, usługa pozorowana jest wstrzykiwana do suta przez test. Usługa pozorowania zastępuje aplikację QuoteService
usługą dostarczaną przez aplikację testową o nazwie TestQuoteService
:
IntegrationTests.IndexPageTests.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult<string>(
"Something's interfering with time, Mr. Scarman, " +
"and time is my business.");
}
}
ConfigureTestServices
jest wywoływana, a usługa o określonym zakresie jest zarejestrowana:
[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddScoped<IQuoteService, TestQuoteService>();
});
})
.CreateClient();
//Act
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
var quoteElement = content.QuerySelector("#quote");
// Assert
Assert.Equal("Something's interfering with time, Mr. Scarman, " +
"and time is my business.", quoteElement.Attributes["value"].Value);
}
Znacznik wygenerowany podczas wykonywania testu odzwierciedla tekst cudzysłowu dostarczony przez TestQuoteService
element , w związku z czym asercji przechodzi:
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Pozorowanie uwierzytelniania
Testy w AuthTests
klasie sprawdzają, czy bezpieczny punkt końcowy:
- Przekierowuje nieuwierzytelnionego użytkownika do strony logowania aplikacji.
- Zwraca zawartość dla uwierzytelnionego użytkownika.
Na stronie SUT /SecurePage
jest używana AuthorizePage konwencja stosowania elementu AuthorizeFilter do strony. Aby uzyskać więcej informacji, zobacz Razor Konwencje autoryzacji stron.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
W teście Get_SecurePageRedirectsAnUnauthenticatedUser
parametr jest WebApplicationFactoryClientOptions ustawiony tak, aby nie zezwalał na przekierowania, ustawiając wartość AllowAutoRedirect na false
:
[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
// Arrange
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
// Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.StartsWith("http://localhost/Identity/Account/Login",
response.Headers.Location.OriginalString);
}
Nie zezwalając klientowi na obserwowanie przekierowania, można wykonać następujące kontrole:
- Kod stanu zwrócony przez sut można sprawdzić względem oczekiwanego HttpStatusCode.Redirect wyniku, a nie końcowy kod stanu po przekierowaniu do strony Logowania, która byłaby .HttpStatusCode.OK
- Wartość nagłówka
Location
w nagłówkach odpowiedzi jest sprawdzana, aby potwierdzić, że rozpoczyna się odhttp://localhost/Identity/Account/Login
, a nie końcowej odpowiedzi strony logowania, gdzieLocation
nagłówek nie będzie obecny.
Aplikacja testowa może wyśmiewać element AuthenticationHandler<TOptions> w ConfigureTestServices celu przetestowania aspektów uwierzytelniania i autoryzacji. Minimalny scenariusz zwraca wartość :AuthenticateResult.Success
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Element TestAuthHandler
jest wywoływany w celu uwierzytelnienia użytkownika, gdy schemat uwierzytelniania jest ustawiony na Test
miejsce, w którym AddAuthentication
jest zarejestrowany dla ConfigureTestServices
programu . Ważne jest, Test
aby schemat był zgodny ze schematem oczekiwanym przez aplikację. W przeciwnym razie uwierzytelnianie nie będzie działać.
[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"Test", options => {});
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Test");
//Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Aby uzyskać więcej informacji na WebApplicationFactoryClientOptions
temat programu , zobacz sekcję Opcje klienta.
Ustawianie środowiska
Domyślnie środowisko hostów i aplikacji sut jest skonfigurowane do korzystania ze środowiska deweloperskiego. Aby zastąpić środowisko SUT podczas korzystania z polecenia IHostBuilder
:
- Ustaw zmienną
ASPNETCORE_ENVIRONMENT
środowiskową (na przykład ,Production
Staging
, lub inną wartość niestandardową, taką jakTesting
). - Zastąpij
CreateHostBuilder
w aplikacji testowej, aby odczytać zmienne środowiskowe poprzedzone prefiksemASPNETCORE
.
protected override IHostBuilder CreateHostBuilder() =>
base.CreateHostBuilder()
.ConfigureHostConfiguration(
config => config.AddEnvironmentVariables("ASPNETCORE"));
Jeśli sut używa hosta sieci Web (IWebHostBuilder
), zastąpij CreateWebHostBuilder
:
protected override IWebHostBuilder CreateWebHostBuilder() =>
base.CreateWebHostBuilder().UseEnvironment("Testing");
Jak infrastruktura testowa wywnioskuje ścieżkę główną zawartości aplikacji
Konstruktor WebApplicationFactory
wywnioskuje ścieżkę katalogu głównego zawartości aplikacji, wyszukując element WebApplicationFactoryContentRootAttribute w zestawie zawierającym testy integracji z kluczem równym zestawowi System.Reflection.Assembly.FullName
TEntryPoint
. Jeśli atrybut z poprawnym kluczem nie zostanie znaleziony, WebApplicationFactory
wróć do wyszukiwania pliku rozwiązania (.sln) i dołącza TEntryPoint
nazwę zestawu do katalogu rozwiązania. Katalog główny aplikacji (ścieżka główna zawartości) służy do odnajdywania widoków i plików zawartości.
Wyłączanie kopiowania w tle
Kopiowanie w tle powoduje wykonanie testów w innym katalogu niż katalog wyjściowy. Jeśli testy polegają na ładowaniu plików względem Assembly.Location
i napotkaniu problemów, może być konieczne wyłączenie kopiowania w tle.
Aby wyłączyć kopiowanie w tle podczas korzystania z narzędzia xUnit, utwórz xunit.runner.json
plik w katalogu projektu testowego z poprawnym ustawieniem konfiguracji:
{
"shadowCopy": false
}
Usuwanie obiektów
Po wykonaniu IClassFixture
TestServer testów implementacji i HttpClient usunięciu elementu xUnit z klasy WebApplicationFactory
. Jeśli obiekty utworzone przez dewelopera wymagają usunięcia, należy je usunąć w implementacji IClassFixture
. Aby uzyskać więcej informacji, zobacz Implementowanie metody Dispose.
Przykład testów integracji
Przykładowa aplikacja składa się z dwóch aplikacji:
Aplikacja | Katalog projektu | opis |
---|---|---|
Aplikacja komunikatów (SUT) | src/RazorPagesProject |
Umożliwia użytkownikowi dodawanie, usuwanie jednego, usuwanie wszystkich i analizowanie komunikatów. |
Testowanie aplikacji | tests/RazorPagesProject.Tests |
Służy do testowania integracji sut. |
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 tests/RazorPagesProject.Tests
katalogu:
dotnet test
Organizacja aplikacji komunikatów (SUT)
SUT 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 (średnie słowa 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 warstwę dostępu do danych (DAL) w swojej klasie
AppDbContext
kontekstu bazy danych (Data/AppDbContext.cs
). - Jeśli baza danych jest pusta podczas uruchamiania aplikacji, magazyn komunikatów jest inicjowany z trzema komunikatami.
- Aplikacja zawiera element
/SecurePage
, do którego można uzyskać dostęp tylko przez uwierzytelnionego użytkownika.
† 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 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 Logika kontrolera testów (przykład implementuje wzorzec repozytorium).
Testowanie organizacji aplikacji
Aplikacja testowa jest aplikacją konsolową wewnątrz tests/RazorPagesProject.Tests
katalogu.
Testowanie katalogu aplikacji | opis |
---|---|
AuthTests |
Zawiera metody testowe dla:
|
BasicTests |
Zawiera metodę testową routingu i typu zawartości. |
IntegrationTests |
Zawiera testy integracji dla strony Indeks przy użyciu klasy niestandardowej WebApplicationFactory . |
Helpers/Utilities |
|
Struktura testowa to xUnit. Testy integracji są przeprowadzane przy użyciu elementu Microsoft.AspNetCore.TestHost, który zawiera TestServerelement . Microsoft.AspNetCore.Mvc.Testing
Ponieważ pakiet jest używany do konfigurowania hosta testowego i serwera testowego, TestHost
pakiety i TestServer
nie wymagają odwołań do bezpośredniego pakietu w pliku projektu aplikacji testowej ani konfiguracji dewelopera w aplikacji testowej.
Testy integracji zwykle wymagają małego zestawu danych w bazie danych przed wykonaniem testu. Na przykład test usuwania wywołuje usunięcie rekordu bazy danych, więc baza danych musi mieć co najmniej jeden rekord, aby żądanie usunięcia powiodło się.
Przykładowa aplikacja wyskakuje bazę danych z trzema komunikatami w Utilities.cs
tych testach, które mogą być używane podczas wykonywania:
public static void InitializeDbForTests(ApplicationDbContext db)
{
db.Messages.AddRange(GetSeedingMessages());
db.SaveChanges();
}
public static void ReinitializeDbForTests(ApplicationDbContext db)
{
db.Messages.RemoveRange(db.Messages);
InitializeDbForTests(db);
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
new Message(){ Text = "TEST RECORD: To the rational mind, " +
"nothing is inexplicable; only unexplained." }
};
}
Kontekst bazy danych SUT jest zarejestrowany w swojej Startup.ConfigureServices
metodzie. Wywołanie zwrotne aplikacji builder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacji Startup.ConfigureServices
. Aby użyć innej bazy danych do testów, kontekst bazy danych aplikacji musi zostać zastąpiony w pliku builder.ConfigureServices
. Aby uzyskać więcej informacji, zobacz sekcję Customize WebApplicationFactory (Dostosowywanie elementu WebApplicationFactory ).
W przypadku jednostek UWIERZYTELNIANIA, które nadal korzystają z hosta internetowego, wywołanie zwrotne aplikacji builder.ConfigureServices
testowej jest wykonywane przed kodem SUT Startup.ConfigureServices
. Wywołanie zwrotne aplikacji testowej builder.ConfigureTestServices
jest wykonywane po.
Dodatkowe zasoby
W tym artykule przyjęto założenie, że podstawowa wiedza na temat testów jednostkowych. Jeśli nie masz pewności co do pojęć związanych z testowaniem, zobacz artykuł Unit Testing in .NET Core and .NET Standard (Testowanie jednostkowe na platformie .NET Core i .NET Standard ) oraz jego połączoną zawartość.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Przykładowa aplikacja to Razor aplikacja Pages i przyjmuje podstawową wiedzę na temat Razor stron. Jeśli nie Razor znasz stron, zobacz następujące artykuły:
Do testowania spAs zalecamy narzędzie, takie jak Playwright dla platformy .NET, które może zautomatyzować przeglądarkę.
Wprowadzenie do testów integracji
Testy integracji oceniają składniki aplikacji na szerszym poziomie niż testy jednostkowe. Testy jednostkowe służą do testowania izolowanych składników oprogramowania, takich jak poszczególne metody klasy. Testy integracji potwierdzają, że co najmniej dwa składniki aplikacji współpracują ze sobą, aby wygenerować oczekiwany wynik, prawdopodobnie włącznie z każdym składnikiem wymaganym do pełnego przetworzenia żądania.
Te szersze testy służą do testowania infrastruktury aplikacji i całej struktury, często w tym następujących składników:
- baza danych
- System plików
- Urządzenia sieciowe
- Potok żądania odpowiedzi
Testy jednostkowe używają sfabrykowanych składników, znanych jako fałszywe lub makiety obiektów, zamiast składników infrastruktury.
W przeciwieństwie do testów jednostkowych, testy integracji:
- Użyj rzeczywistych składników używanych przez aplikację w środowisku produkcyjnym.
- Wymagaj więcej kodu i przetwarzania danych.
- Uruchamianie trwa dłużej.
W związku z tym ogranicz użycie testów integracji do najważniejszych scenariuszy infrastruktury. Jeśli zachowanie można przetestować przy użyciu testu jednostkowego lub testu integracji, wybierz test jednostkowy.
W dyskusjach na temat testów integracji testowany projekt jest często nazywany systemowym testem lub "SUT" w skrócie. Element "SUT" jest używany w tym artykule do odwoływania się do testowanej aplikacji ASP.NET Core.
Nie zapisuj testów integracji dla każdej permutacji danych i dostępu do plików za pomocą baz danych i systemów plików. Niezależnie od liczby miejsc w aplikacji współdziała z bazami danych i systemami plików, skoncentrowany zestaw testów integracji odczytu, zapisu, aktualizacji i usuwania zwykle umożliwia odpowiednie testowanie składników bazy danych i systemu plików. Używaj testów jednostkowych do rutynowych testów logiki metod, które wchodzą w interakcje z tymi składnikami. W testach jednostkowych użycie fałszywych lub pozorów infrastruktury skutkuje szybszym wykonaniem testu.
testy integracji ASP.NET Core
Testy integracji w programie ASP.NET Core wymagają następujących elementów:
- Projekt testowy służy do zawierania i wykonywania testów. Projekt testowy zawiera odwołanie do sutu.
- Projekt testowy tworzy testowego hosta internetowego dla sut i używa klienta serwera testowego do obsługi żądań i odpowiedzi przy użyciu sut.
- Moduł uruchamiający testy służy do wykonywania testów i zgłaszania wyników testu.
Testy integracji są zgodne z sekwencją zdarzeń obejmujących zwykłe kroki testu Rozmieszczanie, Działanie i Asercja :
- Host internetowy SUT jest skonfigurowany.
- Klient serwera testowego jest tworzony w celu przesyłania żądań do aplikacji.
- Krok Rozmieść test jest wykonywany: aplikacja testowa przygotowuje żądanie.
- Krok testu aktu jest wykonywany: klient przesyła żądanie i odbiera odpowiedź.
- Krok testu potwierdzenia jest wykonywany: rzeczywista odpowiedź jest weryfikowana jako powodzenie lub niepowodzenie w oparciu o oczekiwaną odpowiedź.
- Proces będzie kontynuowany do momentu wykonania wszystkich testów.
- Wyniki testów są zgłaszane.
Zazwyczaj testowy host internetowy jest skonfigurowany inaczej niż normalny host internetowy aplikacji na potrzeby przebiegów testu. Na przykład do testów mogą być używane różne ustawienia bazy danych lub różnych aplikacji.
Składniki infrastruktury, takie jak testowy host internetowy i serwer testowy w pamięci (TestServer), są dostarczane lub zarządzane przez pakiet Microsoft.AspNetCore.Mvc.Testing . Użycie tego pakietu usprawnia tworzenie i wykonywanie testów.
Pakiet Microsoft.AspNetCore.Mvc.Testing
obsługuje następujące zadania:
- Kopiuje plik zależności (
.deps
) z sutu do katalogu projektu testowegobin
. - Ustawia katalog główny zawartości na katalog główny projektu SUT, tak aby pliki statyczne i strony/widoki zostały znalezione podczas wykonywania testów.
- Udostępnia klasę WebApplicationFactory , aby usprawnić uruchamianie aplikacji SUT za pomocą polecenia
TestServer
.
W dokumentacji testów jednostkowych opisano sposób konfigurowania projektu testowego i modułu uruchamiającego testy oraz szczegółowe instrukcje dotyczące uruchamiania testów i zaleceń dotyczących sposobu nazywania testów i klas testowych.
Oddziel testy jednostkowe od testów integracji do różnych projektów. Oddzielanie testów:
- Pomaga zagwarantować, że składniki testowania infrastruktury nie zostaną przypadkowo uwzględnione w testach jednostkowych.
- Umożliwia kontrolę nad tym, który zestaw testów jest uruchamiany.
Praktycznie nie ma różnicy między konfiguracją testów Razor aplikacji Pages i aplikacji MVC. Jedyną różnicą jest sposób, w jaki testy są nazwane. Razor W aplikacji Pages testy punktów końcowych strony są zwykle nazwane po klasie modelu strony (na przykład IndexPageTests
w celu przetestowania integracji składników dla strony Indeks). W aplikacji MVC testy są zwykle zorganizowane według klas kontrolerów i nazwane po testowych kontrolerach (na przykład HomeControllerTests
do testowania integracji składników dla Home kontrolera).
Testowanie wymagań wstępnych aplikacji
Projekt testowy musi:
- Utwórz odwołanie do pakietu
Microsoft.AspNetCore.Mvc.Testing
. - Określ zestaw Web SDK w pliku projektu (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Te wymagania wstępne można zobaczyć w przykładowej aplikacji. tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
Sprawdź plik. Przykładowa aplikacja używa struktury testowej xUnit i biblioteki analizatora AngleSharp , więc przykładowa aplikacja również odwołuje się do:
W aplikacjach korzystających z xunit.runner.visualstudio
wersji 2.4.2 lub nowszej Microsoft.NET.Test.Sdk
projekt testowy musi odwoływać się do pakietu.
Program Entity Framework Core jest również używany w testach. Zobacz plik projektu w usłudze GitHub.
Środowisko SUT
Jeśli środowisko SUT nie jest ustawione, środowisko jest domyślnie ustawione na Programowanie.
Podstawowe testy z domyślną funkcją WebApplicationFactory
Uwidaczniaj niejawnie zdefiniowaną Program
klasę w projekcie testowym, wykonując jedną z następujących czynności:
Uwidaczniaj typy wewnętrzne z aplikacji internetowej do projektu testowego. Można to zrobić w pliku projektu SUT (
.csproj
):<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>
Ustaw klasę
Program
jako publiczną przy użyciu deklaracji klasy częściowej:var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }
Przykładowa aplikacja używa
Program
metody klasy częściowej.
WebApplicationFactory<TEntryPoint> służy do tworzenia elementu TestServer dla testów integracji. TEntryPoint
jest klasą punktu wejścia sut, zwykle Program.cs
.
Klasy testowe implementują interfejs oprawy klasy (IClassFixture
), aby wskazać, że klasa zawiera testy i udostępnia wystąpienia obiektów udostępnionych w testach w klasie.
Następująca klasa BasicTests
testowa , używa WebApplicationFactory
metody , aby uruchomić sut i udostępnić metodę HttpClient testową . Get_EndpointsReturnSuccessAndCorrectContentType
Metoda sprawdza, czy kod stanu odpowiedzi zakończył się pomyślnie (200–299), a Content-Type
nagłówek znajduje się text/html; charset=utf-8
na kilku stronach aplikacji.
CreateClient() Program tworzy wystąpienie HttpClient
, które automatycznie śledzi przekierowania i obsługuje pliki cookie.
public class BasicTests
: IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public BasicTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Domyślnie pliki cookie inne niż podstawowe nie są zachowywane w żądaniach, gdy zasady zgody ogólnego rozporządzenia o ochronie danych są włączone. Aby zachować nieistniene pliki cookie, takie jak te używane przez dostawcę TempData, oznacz je jako niezbędne w testach. Aby uzyskać instrukcje dotyczące oznaczania cookie elementu jako niezbędnego, zobacz Podstawowe pliki cookie.
AngleSharp a Application Parts
kontrole antyforgery
W tym artykule użyto analizatora AngleSharp do obsługi testów antyforgeryjnych przez ładowanie stron i analizowanie kodu HTML. Aby przetestować punkty końcowe widoków kontrolera i Razor stron na niższym poziomie, bez dbania o sposób renderowania w przeglądarce, rozważ użycie polecenia Application Parts
. Metoda Części aplikacji wprowadza kontroler lub Razor stronę do aplikacji, która może służyć do tworzenia żądań JSON w celu uzyskania wymaganych wartości. Aby uzyskać więcej informacji, zobacz blog Integration Testing ASP.NET Core Resources Protected with Antiforgery Using Application Parts and associated GitHub repo by Martin Costello (Testowanie integracji ASP.NET core resources protected with Antiforgery Using Application Parts and associated GitHub repo by Martin Costello).
Dostosowywanie elementu WebApplicationFactory
Konfigurację hosta internetowego można utworzyć niezależnie od klas testowych, dziedzicząc z WebApplicationFactory<TEntryPoint> w celu utworzenia co najmniej jednej fabryki niestandardowej:
Dziedzicz z
WebApplicationFactory
i przesłaniaj ConfigureWebHost. Element IWebHostBuilder umożliwia konfigurację kolekcji usług za pomocą poleceniaIWebHostBuilder.ConfigureServices
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { var dbContextDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)); services.Remove(dbContextDescriptor); var dbConnectionDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbConnection)); services.Remove(dbConnectionDescriptor); // Create open SqliteConnection so EF won't automatically close it. services.AddSingleton<DbConnection>(container => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); return connection; }); services.AddDbContext<ApplicationDbContext>((container, options) => { var connection = container.GetRequiredService<DbConnection>(); options.UseSqlite(connection); }); }); builder.UseEnvironment("Development"); } }
Rozmieszczanie bazy danych w przykładowej aplikacji jest wykonywane przez metodę
InitializeDbForTests
. Metoda została opisana w sekcji Przykładowe testy integracji: Testowanie organizacji aplikacji.Kontekst bazy danych SUT jest zarejestrowany w pliku
Program.cs
. Wywołanie zwrotne aplikacjibuilder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacjiProgram.cs
. Aby użyć innej bazy danych do testów niż baza danych aplikacji, kontekst bazy danych aplikacji musi zostać zastąpiony w plikubuilder.ConfigureServices
.Przykładowa aplikacja znajduje deskryptor usługi dla kontekstu bazy danych i używa deskryptora do usunięcia rejestracji usługi. Następnie fabryka dodaje nowy
ApplicationDbContext
, który używa bazy danych w pamięci do testów.Aby nawiązać połączenie z inną bazą danych, zmień wartość
DbConnection
. Aby użyć testowej bazy danych programu SQL Server:
- Odwołanie do
Microsoft.EntityFrameworkCore.SqlServer
pakietu NuGet w pliku projektu. - Wywołaj polecenie
UseInMemoryDatabase
.
Użyj niestandardowego
CustomWebApplicationFactory
w klasach testowych. W poniższym przykładzie użyto fabrykiIndexPageTests
w klasie :public class IndexPageTests : IClassFixture<CustomWebApplicationFactory<Program>> { private readonly HttpClient _client; private readonly CustomWebApplicationFactory<Program> _factory; public IndexPageTests( CustomWebApplicationFactory<Program> factory) { _factory = factory; _client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); }
Klient przykładowej aplikacji jest skonfigurowany tak, aby zapobiec
HttpClient
następującym przekierowaniom. Jak wyjaśniono w dalszej części sekcji uwierzytelnianie mock, pozwala to testom sprawdzić wynik pierwszej odpowiedzi aplikacji. Pierwsza odpowiedź to przekierowanie w wielu z tych testów z nagłówkiemLocation
.Typowy test używa
HttpClient
metod pomocnika i do przetwarzania żądania i odpowiedzi:[Fact] public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot() { // Arrange var defaultPage = await _client.GetAsync("/"); var content = await HtmlHelpers.GetDocumentAsync(defaultPage); //Act var response = await _client.SendAsync( (IHtmlFormElement)content.QuerySelector("form[id='messages']"), (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']")); // Assert Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal("/", response.Headers.Location.OriginalString); }
Każde żądanie POST do SUT musi spełniać kontrolę antyforgeryjną, która jest automatycznie dokonana przez system ochrony danych aplikacji. Aby zorganizować żądanie POST testu, aplikacja testowa musi:
- Utwórz żądanie dla strony.
- Przeanalizuj token antyforgery i cookie żądaj weryfikacji z odpowiedzi.
- Utwórz żądanie POST z tokenem antyforgery i cookie zażądaj weryfikacji.
SendAsync
Metody rozszerzenia pomocnika (Helpers/HttpClientExtensions.cs
) i GetDocumentAsync
metoda pomocnika (Helpers/HtmlHelpers.cs
) w przykładowej aplikacji używają analizatora AngleSharp do obsługi sprawdzania antyforgery przy użyciu następujących metod:
GetDocumentAsync
: Odbiera element HttpResponseMessage i zwraca wartośćIHtmlDocument
.GetDocumentAsync
używa fabryki, która przygotowuje wirtualną odpowiedź na podstawie oryginalnegoHttpResponseMessage
. Aby uzyskać więcej informacji, zobacz dokumentację AngleSharp.SendAsync
metody rozszerzenia dla redagowaniaHttpClient
i HttpRequestMessage wywołania SendAsync(HttpRequestMessage) w celu przesłania żądań do SUT. Przeciążenia akceptowaniaSendAsync
formularza HTML (IHtmlFormElement
) i następujących elementów:- Przycisk Prześlij formularza (
IHtmlElement
) - Kolekcja wartości formularza (
IEnumerable<KeyValuePair<string, string>>
) - Przycisk Prześlij (
IHtmlElement
) i wartości formularza (IEnumerable<KeyValuePair<string, string>>
)
- Przycisk Prześlij formularza (
AngleSharp to biblioteka analizy innej firmy używana do celów demonstracyjnych w tym artykule i przykładowej aplikacji. Funkcja AngleSharp nie jest obsługiwana ani wymagana do testowania integracji aplikacji ASP.NET Core. Można użyć innych analizatorów, takich jak Pakiet Zwinności HTML (HAP). Innym podejściem jest napisanie kodu do obsługi tokenu weryfikacji żądania systemu ochrony przed fałszerzami i bezpośredniego zwalczania.cookie Aby uzyskać więcej informacji, zobacz AngleSharp vs for antiforgery checks in this article (Kontrola kątowa i Application Parts
antyforgeryjna w tym artykule).
Dostawca bazy danych EF-Core w pamięci może służyć do ograniczonego i podstawowego testowania, jednak dostawca SQLite jest zalecanym wyborem do testowania w pamięci.
Zobacz Rozszerzanie uruchamiania za pomocą filtrów uruchamiania, które pokazują, jak skonfigurować oprogramowanie pośredniczące przy użyciu programu IStartupFilter, co jest przydatne, gdy test wymaga niestandardowej usługi lub oprogramowania pośredniczącego.
Dostosowywanie klienta za pomocą programu WithWebHostBuilder
Jeśli wymagana jest dodatkowa konfiguracja w metodzie testowej, WithWebHostBuilder tworzy nową WebApplicationFactory
z elementem IWebHostBuilder , który jest jeszcze bardziej dostosowany przez konfigurację.
Przykładowe wywołania WithWebHostBuilder
kodu w celu zastąpienia skonfigurowanych usług wycinkami testowymi. Aby uzyskać więcej informacji i przykładowe użycie, zobacz Wstrzykiwanie usług makiety w tym artykule.
Metoda Post_DeleteMessageHandler_ReturnsRedirectToRoot
testowa przykładowej aplikacji demonstruje użycie metody WithWebHostBuilder
. Ten test wykonuje usuwanie rekordów w bazie danych, wyzwalając przesłanie formularza w sut.
Ponieważ inny test w IndexPageTests
klasie wykonuje operację, która usuwa wszystkie rekordy w bazie danych i może zostać uruchomiona przed Post_DeleteMessageHandler_ReturnsRedirectToRoot
metodą, baza danych jest ponownie przesyłana w tej metodzie testowej, aby upewnić się, że rekord jest obecny, aby sut usunąć. Wybranie pierwszego przycisku messages
usuwania formularza w sut jest symulowane w żądaniu do sut:
[Fact]
public async Task Post_DeleteMessageHandler_ReturnsRedirectToRoot()
{
// Arrange
using (var scope = _factory.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
Utilities.ReinitializeDbForTests(db);
}
var defaultPage = await _client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
//Act
var response = await _client.SendAsync(
(IHtmlFormElement)content.QuerySelector("form[id='messages']"),
(IHtmlButtonElement)content.QuerySelector("form[id='messages']")
.QuerySelector("div[class='panel-body']")
.QuerySelector("button"));
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
Opcje klienta
Zobacz stronę zawierającą WebApplicationFactoryClientOptions ustawienia domyślne i dostępne opcje podczas tworzenia HttpClient
wystąpień.
Utwórz klasę WebApplicationFactoryClientOptions
i przekaż ją do CreateClient() metody :
public class IndexPageTests :
IClassFixture<CustomWebApplicationFactory<Program>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Program>
_factory;
public IndexPageTests(
CustomWebApplicationFactory<Program> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
UWAGA: Aby uniknąć ostrzeżeń przekierowania HTTPS w dziennikach podczas korzystania z oprogramowania pośredniczącego przekierowania HTTPS, ustaw BaseAddress = new Uri("https://localhost")
Wstrzykiwanie makiety usług
Usługi można zastąpić w teście za pomocą wywołania metody ConfigureTestServices w konstruktorze hosta. Aby ograniczyć zakres przesłonięć usług do samego testu, WithWebHostBuilder metoda jest używana do pobierania konstruktora hostów. Można to zobaczyć w następujących testach:
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
Przykładowy kod SUT zawiera usługę o określonym zakresie, która zwraca cudzysłów. Cudzysłów jest osadzony w ukrytym polu na stronie Indeks po zażądaniu strony Indeks.
Services/IQuoteService.cs
:
public interface IQuoteService
{
Task<string> GenerateQuote();
}
Services/QuoteService.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Dr. Who: Planet of Evil
// https://www.bbc.co.uk/programmes/p00pyrx6
public class QuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult<string>(
"Come on, Sarah. We've an appointment in London, " +
"and we're already 30,000 years late.");
}
}
Program.cs
:
services.AddScoped<IQuoteService, QuoteService>();
Pages/Index.cshtml.cs
:
public class IndexModel : PageModel
{
private readonly ApplicationDbContext _db;
private readonly IQuoteService _quoteService;
public IndexModel(ApplicationDbContext db, IQuoteService quoteService)
{
_db = db;
_quoteService = quoteService;
}
[BindProperty]
public Message Message { get; set; }
public IList<Message> Messages { get; private set; }
[TempData]
public string MessageAnalysisResult { get; set; }
public string Quote { get; private set; }
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
Quote = await _quoteService.GenerateQuote();
}
Pages/Index.cs
:
<input id="quote" type="hidden" value="@Model.Quote">
Po uruchomieniu aplikacji SUT jest generowany następujący znacznik:
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Aby przetestować usługę i zacytować iniekcję w teście integracji, usługa pozorowana jest wstrzykiwana do suta przez test. Usługa pozorowania zastępuje aplikację QuoteService
usługą dostarczaną przez aplikację testową o nazwie TestQuoteService
:
IntegrationTests.IndexPageTests.cs
:
// Quote ©1975 BBC: The Doctor (Tom Baker); Pyramids of Mars
// https://www.bbc.co.uk/programmes/p00pys55
public class TestQuoteService : IQuoteService
{
public Task<string> GenerateQuote()
{
return Task.FromResult(
"Something's interfering with time, Mr. Scarman, " +
"and time is my business.");
}
}
ConfigureTestServices
jest wywoływana, a usługa o określonym zakresie jest zarejestrowana:
[Fact]
public async Task Get_QuoteService_ProvidesQuoteInPage()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddScoped<IQuoteService, TestQuoteService>();
});
})
.CreateClient();
//Act
var defaultPage = await client.GetAsync("/");
var content = await HtmlHelpers.GetDocumentAsync(defaultPage);
var quoteElement = content.QuerySelector("#quote");
// Assert
Assert.Equal("Something's interfering with time, Mr. Scarman, " +
"and time is my business.", quoteElement.Attributes["value"].Value);
}
Znacznik wygenerowany podczas wykonywania testu odzwierciedla tekst cudzysłowu dostarczony przez TestQuoteService
element , w związku z czym asercji przechodzi:
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Pozorowanie uwierzytelniania
Testy w AuthTests
klasie sprawdzają, czy bezpieczny punkt końcowy:
- Przekierowuje nieuwierzytelnionego użytkownika do strony logowania aplikacji.
- Zwraca zawartość dla uwierzytelnionego użytkownika.
Na stronie SUT /SecurePage
jest używana AuthorizePage konwencja stosowania elementu AuthorizeFilter do strony. Aby uzyskać więcej informacji, zobacz Razor Konwencje autoryzacji stron.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
W teście Get_SecurePageRedirectsAnUnauthenticatedUser
parametr jest WebApplicationFactoryClientOptions ustawiony tak, aby nie zezwalał na przekierowania, ustawiając wartość AllowAutoRedirect na false
:
[Fact]
public async Task Get_SecurePageRedirectsAnUnauthenticatedUser()
{
// Arrange
var client = _factory.CreateClient(
new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
// Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.StartsWith("http://localhost/Identity/Account/Login",
response.Headers.Location.OriginalString);
}
Nie zezwalając klientowi na obserwowanie przekierowania, można wykonać następujące kontrole:
- Kod stanu zwrócony przez sut można sprawdzić względem oczekiwanego HttpStatusCode.Redirect wyniku, a nie końcowy kod stanu po przekierowaniu do strony logowania, która byłaby .HttpStatusCode.OK
- Wartość nagłówka
Location
w nagłówkach odpowiedzi jest sprawdzana, aby potwierdzić, że rozpoczyna się odhttp://localhost/Identity/Account/Login
, a nie końcowej odpowiedzi strony logowania, w którejLocation
nagłówek nie będzie obecny.
Aplikacja testowa może wyśmiewać element AuthenticationHandler<TOptions> w ConfigureTestServices celu przetestowania aspektów uwierzytelniania i autoryzacji. Minimalny scenariusz zwraca wartość :AuthenticateResult.Success
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "TestScheme");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Element TestAuthHandler
jest wywoływany w celu uwierzytelnienia użytkownika, gdy schemat uwierzytelniania jest ustawiony na TestScheme
miejsce, w którym AddAuthentication
jest zarejestrowany dla ConfigureTestServices
programu . Ważne jest, TestScheme
aby schemat był zgodny ze schematem oczekiwanym przez aplikację. W przeciwnym razie uwierzytelnianie nie będzie działać.
[Fact]
public async Task Get_SecurePageIsReturnedForAnAuthenticatedUser()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(defaultScheme: "TestScheme")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"TestScheme", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(scheme: "TestScheme");
//Act
var response = await client.GetAsync("/SecurePage");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Aby uzyskać więcej informacji na WebApplicationFactoryClientOptions
temat programu , zobacz sekcję Opcje klienta.
Podstawowe testy oprogramowania pośredniczącego uwierzytelniania
Zobacz to repozytorium GitHub, aby zapoznać się z podstawowymi testami oprogramowania pośredniczącego uwierzytelniania. Zawiera on serwer testowy specyficzny dla scenariusza testowego.
Ustawianie środowiska
Ustaw środowisko w niestandardowej fabryce aplikacji:
public class CustomWebApplicationFactory<TProgram>
: WebApplicationFactory<TProgram> where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var dbContextDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
services.Remove(dbContextDescriptor);
var dbConnectionDescriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbConnection));
services.Remove(dbConnectionDescriptor);
// Create open SqliteConnection so EF won't automatically close it.
services.AddSingleton<DbConnection>(container =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
services.AddDbContext<ApplicationDbContext>((container, options) =>
{
var connection = container.GetRequiredService<DbConnection>();
options.UseSqlite(connection);
});
});
builder.UseEnvironment("Development");
}
}
Jak infrastruktura testowa wywnioskuje ścieżkę główną zawartości aplikacji
Konstruktor WebApplicationFactory
wywnioskuje ścieżkę katalogu głównego zawartości aplikacji, wyszukując element WebApplicationFactoryContentRootAttribute w zestawie zawierającym testy integracji z kluczem równym zestawowi System.Reflection.Assembly.FullName
TEntryPoint
. Jeśli atrybut z poprawnym kluczem nie zostanie znaleziony, WebApplicationFactory
wróć do wyszukiwania pliku rozwiązania (.sln) i dołącza TEntryPoint
nazwę zestawu do katalogu rozwiązania. Katalog główny aplikacji (ścieżka główna zawartości) służy do odnajdywania widoków i plików zawartości.
Wyłączanie kopiowania w tle
Kopiowanie w tle powoduje wykonanie testów w innym katalogu niż katalog wyjściowy. Jeśli testy polegają na ładowaniu plików względem Assembly.Location
i napotkaniu problemów, może być konieczne wyłączenie kopiowania w tle.
Aby wyłączyć kopiowanie w tle podczas korzystania z narzędzia xUnit, utwórz xunit.runner.json
plik w katalogu projektu testowego z poprawnym ustawieniem konfiguracji:
{
"shadowCopy": false
}
Usuwanie obiektów
Po wykonaniu IClassFixture
TestServer testów implementacji i HttpClient usunięciu elementu xUnit z klasy WebApplicationFactory
. Jeśli obiekty utworzone przez dewelopera wymagają usunięcia, należy je usunąć w implementacji IClassFixture
. Aby uzyskać więcej informacji, zobacz Implementowanie metody Dispose.
Przykład testów integracji
Przykładowa aplikacja składa się z dwóch aplikacji:
Aplikacja | Katalog projektu | opis |
---|---|---|
Aplikacja komunikatów (SUT) | src/RazorPagesProject |
Umożliwia użytkownikowi dodawanie, usuwanie jednego, usuwanie wszystkich i analizowanie komunikatów. |
Testowanie aplikacji | tests/RazorPagesProject.Tests |
Służy do testowania integracji sut. |
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 tests/RazorPagesProject.Tests
katalogu:
dotnet test
Organizacja aplikacji komunikatów (SUT)
SUT 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 (średnie słowa 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 warstwę dostępu do danych (DAL) w swojej klasie
AppDbContext
kontekstu bazy danych (Data/AppDbContext.cs
). - Jeśli baza danych jest pusta podczas uruchamiania aplikacji, magazyn komunikatów jest inicjowany z trzema komunikatami.
- Aplikacja zawiera element
/SecurePage
, do którego można uzyskać dostęp tylko przez uwierzytelnionego użytkownika.
† Artykuł EF Test with InMemory (Testowanie za pomocą rozwiązania 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 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 Logika kontrolera testów (przykład implementuje wzorzec repozytorium).
Testowanie organizacji aplikacji
Aplikacja testowa jest aplikacją konsolową wewnątrz tests/RazorPagesProject.Tests
katalogu.
Testowanie katalogu aplikacji | opis |
---|---|
AuthTests |
Zawiera metody testowe dla:
|
BasicTests |
Zawiera metodę testową routingu i typu zawartości. |
IntegrationTests |
Zawiera testy integracji dla strony Indeks przy użyciu klasy niestandardowej WebApplicationFactory . |
Helpers/Utilities |
|
Struktura testowa to xUnit. Testy integracji są przeprowadzane przy użyciu elementu Microsoft.AspNetCore.TestHost, który zawiera TestServerelement . Microsoft.AspNetCore.Mvc.Testing
Ponieważ pakiet jest używany do konfigurowania hosta testowego i serwera testowego, TestHost
pakiety i TestServer
nie wymagają odwołań do bezpośredniego pakietu w pliku projektu aplikacji testowej ani konfiguracji dewelopera w aplikacji testowej.
Testy integracji zwykle wymagają małego zestawu danych w bazie danych przed wykonaniem testu. Na przykład test usuwania wywołuje usunięcie rekordu bazy danych, więc baza danych musi mieć co najmniej jeden rekord, aby żądanie usunięcia powiodło się.
Przykładowa aplikacja wyskakuje bazę danych z trzema komunikatami w Utilities.cs
tych testach, które mogą być używane podczas wykonywania:
public static void InitializeDbForTests(ApplicationDbContext db)
{
db.Messages.AddRange(GetSeedingMessages());
db.SaveChanges();
}
public static void ReinitializeDbForTests(ApplicationDbContext db)
{
db.Messages.RemoveRange(db.Messages);
InitializeDbForTests(db);
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "TEST RECORD: You're standing on my scarf." },
new Message(){ Text = "TEST RECORD: Would you like a jelly baby?" },
new Message(){ Text = "TEST RECORD: To the rational mind, " +
"nothing is inexplicable; only unexplained." }
};
}
Kontekst bazy danych SUT jest zarejestrowany w pliku Program.cs
. Wywołanie zwrotne aplikacji builder.ConfigureServices
testowej jest wykonywane po wykonaniu kodu aplikacji Program.cs
. Aby użyć innej bazy danych do testów, kontekst bazy danych aplikacji musi zostać zastąpiony w pliku builder.ConfigureServices
. Aby uzyskać więcej informacji, zobacz sekcję Customize WebApplicationFactory (Dostosowywanie elementu WebApplicationFactory ).