Tests d’intégration dans ASP.NET Core
Par Jos van der Til, Martin Costello et Javier Calvarro Nelson.
Les tests d’intégration garantissent que les composants d’une application fonctionnent correctement à un niveau qui inclut l’infrastructure de prise en charge de l’application, comme la base de données, le système de fichiers et le réseau. ASP.NET Core prend en charge les tests d’intégration à l’aide d’une infrastructure de tests unitaires avec un hôte web de test et un serveur de test en mémoire.
Cet article suppose une connaissance élémentaire des tests unitaires. Si vous ne connaissez pas les concepts de test, consultez l’article Tests unitaires dans .NET Core et .NET Standard et son contenu lié.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
L’exemple d’application est une application Pages Razor et suppose une compréhension de base de Pages Razor. Si vous n’êtes pas familiarisé avec Pages Razor, consultez les articles suivants :
Pour tester les SPA, nous vous recommandons d’utiliser un outil tel que Playwright pour .NET, qui peut automatiser un navigateur.
Présentation des tests d’intégration
Les tests d’intégration évaluent les composants d’une application à un niveau plus large que les tests unitaires. Les tests unitaires sont utilisés pour tester des composants logiciels isolés, tels que des méthodes de classe individuelles. Les tests d’intégration confirment que deux ou plusieurs composants d’application fonctionnent ensemble pour produire un résultat attendu, y compris éventuellement chaque composant requis pour traiter entièrement une requête.
Ces tests plus larges sont utilisés pour tester l’infrastructure et la structure complète de l’application, y compris souvent les composants suivants :
- Base de données
- Système de fichiers
- Appliances réseau
- Pipeline requête-réponse
Les tests unitaires utilisent des composants fabriqués, connus comme des faux ou des objets fictifs, à la place des composants d’infrastructure.
En opposition aux tests unitaires, les tests d’intégration :
- Utilisent les composants réels que l’application utilise en production.
- Exigent davantage de code et de traitement des données.
- Prennent plus de temps pour s’exécuter.
Par conséquent, limitez l’utilisation des tests d’intégration aux scénarios d’infrastructure les plus importants. Si un comportement peut être testé à l’aide d’un test unitaire ou d’un test d’intégration, choisissez le test unitaire.
Dans les discussions sur les tests d’intégration, le projet testé est fréquemment appelé système testé, ou « ST » pour faire court. « ST » est utilisé tout au long de cet article pour faire référence à l’application ASP.NET Core testée.
N’écrivez pas de tests d’intégration pour chaque permutation des données et de l’accès aux fichiers avec des bases de données et des systèmes de fichiers. Quel que soit le nombre d’emplacements d’une application qui interagissent avec des bases de données et des systèmes de fichiers, un ensemble ciblé de tests d’intégration de lecture, d’écriture, de mise à jour et de suppression est généralement capable de tester de manière adéquate les composants de base de données et de système de fichiers. Utilisez des tests unitaires pour les tests de routine de la logique de méthode qui interagissent avec ces composants. Dans les tests unitaires, l’utilisation d’infrastructures fausses ou fictives entraîne une exécution des tests plus rapide.
tests d’intégration ASP.NET Core
Les tests d’intégration dans ASP.NET Core nécessitent les éléments suivants :
- Un projet de test est utilisé pour contenir et exécuter les tests. Le projet de test a une référence au ST.
- Le projet de test crée un hôte web de test pour le ST et utilise un client de serveur de test pour gérer les demandes et les réponses avec le ST.
- Un exécuteur de test est utilisé pour exécuter les tests et livrer les résultats des tests.
Les tests d’intégration suivent une séquence d’événements qui inclut les étapes de test habituelles Arrange, Act et Assert :
- L’hôte web du ST est configuré.
- Un client de serveur de test est créé pour envoyer des requêtes à l’application.
- L’étape de test Arrange est exécutée : l’application de test prépare une requête.
- L’étape de test Act est exécutée : le client envoie la requête et reçoit la réponse.
- L’étape de test Assert est exécutée : la réponse réelle est validée en tant que Réussite ou Échec en fonction d’une réponse attendue.
- Le processus se poursuit jusqu’à ce que tous les tests soient exécutés.
- Les résultats des tests sont livrés.
En règle générale, l’hôte web de test est configuré différemment de l’hôte web normal de l’application pour les séries de tests. Par exemple, une base de données différente ou des paramètres d’application différents peuvent être utilisés pour les tests.
Les composants d’infrastructure, tels que l’hôte web de test et le serveur de test en mémoire (TestServer), sont fournis ou gérés par le package Microsoft.AspNetCore.Mvc.Testing . L’utilisation de ce package simplifie la création et l’exécution des tests.
Le package Microsoft.AspNetCore.Mvc.Testing
gère les tâches suivantes :
- Copie le fichier de dépendances (
.deps
) du ST dans le répertoire du projet de testbin
. - Définit la racine du contenu sur la racine du projet du ST afin que soient trouvés les pages/vues et fichiers statiques quand les tests sont exécutés.
- Il fournit la classe WebApplicationFactory afin de simplifier l’amorçage de l’application testée avec
TestServer
.
La documentation sur les tests unitaires décrit comment configurer un projet de test et un exécuteur de tests, ainsi que des instructions détaillées sur l’exécution des tests et des recommandations pour nommer les tests et les classes de test.
Séparez les tests unitaires des tests d’intégration dans différents projets. Séparation des tests :
- Permet de s’assurer que les composants de test d’infrastructure ne sont pas accidentellement inclus dans les tests unitaires.
- Permet de contrôler quel ensemble de tests sont exécutés.
Il n’existe pratiquement aucune différence entre la configuration des tests des applications Pages Razor et des applications MVC. La seule différence réside dans la façon dont les tests sont nommés. Dans une application Pages Razor, les tests des points de terminaison de page sont généralement nommés d’après la classe de modèle de page (par exemple, IndexPageTests
pour tester l’intégration des composants pour la page Index). Dans une application MVC, les tests sont généralement organisés par classes de contrôleur et nommés d’après les contrôleurs qu’ils testent (par exemple, HomeControllerTests
pour tester l’intégration des composants pour le Home contrôleur).
Conditions préalables pour tester l’application
Le projet de test doit :
- Référencez le package
Microsoft.AspNetCore.Mvc.Testing
. - Spécifier le SDK Web dans le fichier projet (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Ces prérequis sont visibles dans l’échantillon d’application. Examinez le fichier tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
. L’échantillon d’application utilise l’infrastructure de test xUnit et la bibliothèque d’analyseur AngleSharp, de sorte que l’échantillon d’application référence également :
Dans les applications qui utilisent xunit.runner.visualstudio
la version 2.4.2 ou ultérieure, le projet de test doit référencer le package Microsoft.NET.Test.Sdk
.
Entity Framework Core est également utilisé dans les tests. Consultez le fichier projet dans GitHub.
Environnement de ST
Si l’environnement de ST n’est pas paramétré, l’environnement est défini par défaut sur Développement.
Tests de base avec webApplicationFactory par défaut
Exposez la classe Program
implicitement définie au projet de test en effectuant l’une des opérations suivantes :
Exposez les types internes de l’application web au projet de test. Cette opération peut être effectuée dans le fichier du projet de ST (
.csproj
) :<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>
Rendez la classe
Program
publique à l’aide d’une déclaration de classe partielle :var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }
L’échantillon d’application utilise l’approche de classe partielle
Program
.
WebApplicationFactory<TEntryPoint> est utilisé pour créer un TestServer pour les tests d’intégration. TEntryPoint
est la classe de point d’entrée du ST, généralement Program.cs
.
Les classes de test implémentent une interface de fixture de classe (IClassFixture
) pour indiquer que la classe contient des tests et fournir des instances d’objets partagés entre les tests de la classe .
La classe de test suivante, BasicTests
, utilise WebApplicationFactory
pour démarrer le ST et fournir un HttpClient à une méthode de test, Get_EndpointsReturnSuccessAndCorrectContentType
. La méthode vérifie que le code d’état de la réponse a réussi (200-299) et que l’en-tête Content-Type
est text/html; charset=utf-8
sur plusieurs pages d’application.
CreateClient() crée une instance de HttpClient
qui suit automatiquement les redirections et gère les cookies.
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());
}
}
Par défaut, les cookies non essentiels ne sont pas conservés entre les requêtes lorsque la politique de consentement du Règlement général sur la protection des données est activée. Pour conserver les cookies non essentiels , tels que ceux utilisés par le fournisseur TempData, marquez-les comme essentiels dans vos tests. Pour obtenir des instructions comment identifier un cookie comme essentiel, consultez Cookies essentiels.
AngleSharp vs Application Parts
pour les contrôles antifalsification
Cet article utilise l’analyseur AngleSharp pour gérer les vérifications antifalsification en chargeant des pages et en analysant le code HTML. Pour tester les points de terminaison des vues contrôleur et Pages Razor à un niveau inférieur, sans vous soucier de la façon dont ils s’affichent dans le navigateur, pensez à utiliser Application Parts
. L’approche des composants d’application injecte un contrôleur ou une page Razor dans l’application qui peut être utilisé pour effectuer des requêtes JSON afin d’obtenir les valeurs requises. Pour plus d’informations, consultez le blog Integration Testing ASP.NET Core Resources Protected with Antiforgery Using Application Parts et le référentiel GitHub associé par Martin Costello.
Personnaliser WebApplicationFactory
La configuration de l’hôte web peut être créée indépendamment des classes de test en héritant de WebApplicationFactory<TEntryPoint> pour créer une ou plusieurs fabriques personnalisées :
Hériter de
WebApplicationFactory
et remplacer la méthode ConfigureWebHost. IWebHostBuilder autorise la configuration de la collection de services avecIWebHostBuilder.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"); } }
L’amorçage de base de données dans l’échantillon d’application est effectué par la méthode
InitializeDbForTests
. La méthode est décrite dans la section Échantillon de tests d’intégration : organisation de test d’application.Le contexte de base de données du ST est inscrit dans
Program.cs
. Le rappel de l’application de testbuilder.ConfigureServices
est exécuté après l’exécution du code de l’applicationProgram.cs
. Pour utiliser une base de données différente pour les tests de la base de données de l’application, le contexte de base de données de l’application doit être remplacé dansbuilder.ConfigureServices
.L’échantillon d’application trouve le descripteur de service pour le contexte de base de données et utilise le descripteur pour supprimer l’inscription du service. La fabrique ajoute ensuite un nouveau
ApplicationDbContext
qui utilise une base de données en mémoire pour les tests.Pour vous connecter à une autre base de données, modifiez le
DbConnection
. Pour utiliser une base de données de test SQL Server :- Référencez le package NuGet
Microsoft.EntityFrameworkCore.SqlServer
dans le fichier projet. - Appelez
UseInMemoryDatabase
.
- Référencez le package NuGet
Utilisez le
CustomWebApplicationFactory
personnalisé dans les classes de test. L’exemple suivant utilise la fabrique dans la classeIndexPageTests
: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 }); }
Le client de l’échantillon d’application est configuré pour empêcher le
HttpClient
d’effectuer les redirections suivantes. Comme expliqué plus loin dans la section Authentification fictive, cela permet aux tests de vérifier le résultat de la première réponse de l’application. La première réponse est une redirection dans la plupart de ces tests avec un en-têteLocation
.Un test classique utilise le
HttpClient
et les méthodes d’assistance pour traiter la requête et la réponse :[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); }
Toute requête POST adressée au ST doit satisfaire les contrôles antifalsification effectués automatiquement par le système antifalsification de protection des données de l’application. Pour organiser la requête POST d’un test, l’application de test doit :
- Effectuer une requête pour la page.
- Analyser l’antifalsification cookie et demander le jeton de validation à partir de la réponse.
- Effectuer la requête POST avec l’antifalsification cookie et le jeton de validation de la requête en place.
Les méthodes d’extension d’assistance SendAsync
(Helpers/HttpClientExtensions.cs
) et la méthode d’assistance GetDocumentAsync
(Helpers/HtmlHelpers.cs
) dans l’échantillon d’application utilisent l’analyseur AngleSharp pour gérer les contrôles antifalsification avec les méthodes suivantes :
GetDocumentAsync
: Reçoit le HttpResponseMessage et retourne unIHtmlDocument
.GetDocumentAsync
utilise une fabrique qui prépare une réponse virtuelle basée sur l’originalHttpResponseMessage
. Pour plus d’informations, consultez la documentation AngleSharp.- Les méthodes d’extension
SendAsync
pour leHttpClient
composent un HttpRequestMessage et appellent SendAsync(HttpRequestMessage) pour envoyer des demandes au ST. Les surcharges pourSendAsync
acceptent le formulaire HTML (IHtmlFormElement
) et les éléments suivants :- Bouton Envoyer du formulaire (
IHtmlElement
) - Collection de valeurs de formulaire (
IEnumerable<KeyValuePair<string, string>>
) - Bouton Envoyer (
IHtmlElement
) et valeurs de formulaire (IEnumerable<KeyValuePair<string, string>>
)
- Bouton Envoyer du formulaire (
AngleSharp est une bibliothèque d’analyse tierce utilisée à des fins de démonstration dans cet article et dans l’échantillon d’application. AngleSharp n’est pas pris en charge ou requis pour les tests d’intégration des applications ASP.NET Core. D’autres analyseurs peuvent être utilisés, tels que le Html Agility Pack (HAP). Une autre approche consiste à écrire du code pour gérer directement l’antifalsification et le jeton de vérification de requête du système antifalsification cookie. Pour plus d’informations, consultez AngleSharp vs Application Parts
pour les contrôles antifalsification dans cet article.
Le fournisseur de base de données en mémoire EF-Core peut être utilisé pour des tests limités et de base, mais le fournisseur SQLite est le choix recommandé pour les tests en mémoire.
Consultez Étendre le démarrage avec des filtres de démarrage qui montre comment configurer l’intergiciel à l’aide de IStartupFilter, ce qui est utile lorsqu’un test nécessite un service ou un intergiciel personnalisé.
Personnaliser le client avec WithWebHostBuilder
Quand une configuration supplémentaire est requise dans une méthode de test, WithWebHostBuilder crée un WebApplicationFactory
avec un IWebHostBuilder qui est davantage personnalisé par la configuration.
L’exemple de code appelle WithWebHostBuilder
pour remplacer les services configurés par des stubs de test. Pour obtenir plus d’informations et pour des exemples d’utilisation, consultez Injecter des services fictifs dans cet article.
La Post_DeleteMessageHandler_ReturnsRedirectToRoot
méthode de test de l’échantillon d’application illustre l’utilisation de WithWebHostBuilder
. Ce test effectue une suppression d’enregistrement dans la base de données en déclenchant une soumission de formulaire dans le ST.
Étant donné qu’un autre test de la classe IndexPageTests
effectue une opération qui supprime tous les enregistrements de la base de données et peut s’exécuter avant la méthode Post_DeleteMessageHandler_ReturnsRedirectToRoot
, la base de données est réamorcée dans cette méthode de test pour s’assurer qu’un enregistrement est présent pour que le ST puisse supprimer. Sélectionner le premier bouton de suppression du formulaire messages
dans le ST est simulé dans la requête adressée au ST :
[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);
}
Options du client
Consultez la page WebApplicationFactoryClientOptions pour connaître les valeurs par défaut et les options disponibles lors de la création d’instances HttpClient
.
Créez la classe WebApplicationFactoryClientOptions
et passez-la à la méthode CreateClient() :
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
});
}
REMARQUE : Pour éviter les avertissements de redirection HTTPS dans les journaux d’activité, lors de l’utilisation d’intergiciel de redirection HTTPS, définissez BaseAddress = new Uri("https://localhost")
Injecter des services fictifs
Les services peuvent être remplacés dans un test avec un appel à ConfigureTestServices sur le générateur d’hôte. Pour étendre les services remplacés au test lui-même, la méthode WithWebHostBuilder est utilisée pour récupérer un générateur d’hôtes. Vous pouvez le voir dans les tests suivants :
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
L’échantillon de ST inclut un service délimité qui retourne un devis. Le devis est incorporé dans un champ masqué de la page Index lorsque la page Index est demandée.
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">
Le balisage suivant est généré lors de l’exécution de l’application ST :
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Pour tester le service et l’injection de devis dans un test d’intégration, un service fictif est injecté dans le ST par le test. Le service fictif remplace le QuoteService
de l’application par un service fourni par l’application de test, appelé 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
est appelé et le service délimité est inscrit :
[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);
}
Le balisage produit pendant l’exécution du test reflète le texte de guillemet fourni par TestQuoteService
, ainsi l’assertion aboutit :
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Authentification fictive
Les tests dans la classe AuthTests
vérifient qu’un point de terminaison sécurisé :
- Redirige un utilisateur non authentifié vers la page de connexion de l’application.
- Retourne le contenu d’un utilisateur authentifié.
Dans le ST, la page /SecurePage
utilise une convention AuthorizePage pour appliquer un AuthorizeFilter à la page. Pour plus d’informations sur les conventions, consultez Conventions des autorisations de Pages Razor.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Dans le test Get_SecurePageRedirectsAnUnauthenticatedUser
, un WebApplicationFactoryClientOptions est défini pour interdire les redirections en définissant AllowAutoRedirect sur 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);
}
En interdisant au client de suivre la redirection, les vérifications suivantes peuvent être effectuées :
- Le code d’état retourné par le ST peut être vérifié par rapport au résultat HttpStatusCode.Redirect attendu, et non par rapport au code d’état final après la redirection vers la page de connexion, qui serait HttpStatusCode.OK.
- La valeur d’en-tête
Location
dans les en-têtes de réponse est vérifiée pour confirmer qu’elle commence parhttp://localhost/Identity/Account/Login
, et non par la réponse de la page de connexion finale, où l’en-têteLocation
ne serait pas présent.
L’application de test peut simuler un AuthenticationHandler<TOptions> dans ConfigureTestServices afin de tester des aspects de l’authentification et de l’autorisation. Un scénario minimal retourne un 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);
}
}
TestAuthHandler
est appelé pour authentifier un utilisateur lorsque le schéma d’authentification est défini sur TestScheme
quand AddAuthentication
est inscrit pour ConfigureTestServices
. Il est important que le schéma TestScheme
corresponde à celui attendu par votre application. Sinon, l’authentification ne fonctionnera pas.
[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);
}
Pour plus d’informations sur WebApplicationFactoryClientOptions
, consultez la section Options du client.
Tests de base pour l’intergiciel d’authentification
Consultez ce référentiel GitHub pour les tests de base de l’intergiciel d’authentification. Il contient un serveur de test spécifique au scénario de test.
Définir l’environnement
Définissez l’environnement dans la fabrique d’application personnalisée :
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");
}
}
Comment l’infrastructure de test déduit le chemin racine du contenu de l’application
Le constructeur WebApplicationFactory
déduit le chemin racine du contenu de l’application en recherchant un WebApplicationFactoryContentRootAttribute sur l’assembly contenant les tests d’intégration avec une clé égale à TEntryPoint
l’assembly System.Reflection.Assembly.FullName
. Si un attribut avec la clé correcte est introuvable, WebApplicationFactory
revient à rechercher un fichier de solution (.sln) et ajoute le nom de l’assembly TEntryPoint
au répertoire de la solution. Le répertoire racine de l’application (le chemin racine du contenu) est utilisé pour découvrir les vues et les fichiers de contenu.
Désactiver le cliché instantané
Le cliché instantané entraîne l’exécution des tests dans un répertoire différent du répertoire de sortie. Si vos tests reposent sur le chargement de fichiers relatifs à Assembly.Location
et que vous rencontrez des problèmes, vous devrez peut-être désactiver le cliché instantané.
Pour désactiver le cliché instantané lors de l’utilisation de xUnit, créez un fichier xunit.runner.json
dans le répertoire de votre projet de test, avec le paramètre de configuration correct :
{
"shadowCopy": false
}
Élimination des objets
Une fois les tests de l’implémentation IClassFixture
exécutés, TestServer et HttpClient sont supprimés lorsque xUnit supprime le WebApplicationFactory
. Si les objets instanciés par le développeur doivent être éliminés, éliminez-les dans l’implémentation IClassFixture
. Pour plus d’informations, consultez Implémentation d’une méthode Dispose.
Exemple de tests d’intégration
L’échantillon d’application est composé de deux applications :
App | Répertoire du projet | Description |
---|---|---|
Application de message (ST) | src/RazorPagesProject |
Permet à un utilisateur d’ajouter, de supprimer un message, de tout supprimer et d’analyser les messages. |
Tester une application | tests/RazorPagesProject.Tests |
Utilisé pour tester l’intégration du ST. |
Les tests peuvent être exécutés à l’aide des fonctionnalités de test intégrées d’un environnement de développement intégré, telles que Visual Studio. Si vous utilisez Visual Studio Code ou la ligne de commande, exécutez la commande suivante à une invite de commandes dans le répertoire tests/RazorPagesProject.Tests
:
dotnet test
Organisation d’application de message (ST)
Le ST est un système de messages Pages Razor avec les caractéristiques suivantes :
- La page Index de l’application (
Pages/Index.cshtml
etPages/Index.cshtml.cs
) fournit une interface utilisateur et des méthodes de modèle de page pour contrôler l’ajout, la suppression et l’analyse des messages (mots moyens par message). - Un message est décrit par la classe
Message
(Data/Message.cs
) avec deux propriétés :Id
(clé) etText
(message). La propriétéText
est obligatoire et limitée à 200 caractères. - Les messages sont stockés à l’aide de la base de données en mémoire d’Entity Framework†.
- L’application contient une couche d’accès aux données dans sa classe de contexte de base de données,
AppDbContext
(Data/AppDbContext.cs
). - Si la base de données est vide au démarrage de l’application, la banque de messages est initialisée avec trois messages.
- L’application inclut un
/SecurePage
qui n’est accessible qu’à un utilisateur authentifié.
†L’article EF, Tester avec InMemory, explique comment utiliser une base de données en mémoire pour les tests avec MSTest. Cette rubrique utilise l’infrastructure de test xUnit. Les concepts de test et les implémentations de test entre différents frameworks de test sont similaires, mais pas identiques.
Bien que l’application n’utilise pas le modèle de référentiel et ne soit pas un exemple efficace du modèle d’unité de travail (UoW), Razor Pages prend en charge ces modèles de développement. Pour plus d’informations, consultez Conception de la couche de persistance de l’infrastructure et Tester la logique du contrôleur (l’échantillon implémente le modèle de référentiel).
Tester l’organisation de l’application
L’application de test est une application console à l’intérieur du répertoire tests/RazorPagesProject.Tests
.
Répertoire de l’application de test | Description |
---|---|
AuthTests |
Contient des méthodes de test pour :
|
BasicTests |
Contient une méthode de test pour le routage et le type de contenu. |
IntegrationTests |
Contient les tests d’intégration pour la page Index à l’aide de la classe personnalisée WebApplicationFactory . |
Helpers/Utilities |
|
Le framework de test est xUnit. Les tests d’intégration sont effectués à l’aide de Microsoft.AspNetCore.TestHost, qui inclut le TestServer. Étant donné que le package Microsoft.AspNetCore.Mvc.Testing
est utilisé pour configurer l’hôte de test et le serveur de test, les packages TestHost
et TestServer
ne nécessitent pas de références de package directes dans le fichier projet de l’application de test ou la configuration du développeur dans l’application de test.
Les tests d’intégration nécessitent généralement un petit jeu de données dans la base de données avant l’exécution du test. Par exemple, un test de suppression appelle une suppression d’enregistrement de base de données, de sorte que la base de données doit avoir au moins un enregistrement pour que la requête de suppression aboutisse.
L’échantillon d’application amorcera la base de données avec trois messages dans Utilities.cs
que les tests peuvent utiliser lorsqu’ils s’exécutent :
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." }
};
}
Le contexte de base de données du ST est inscrit dans Program.cs
. Le rappel de l’application de test builder.ConfigureServices
est exécuté après l’exécution du code de l’application Program.cs
. Pour utiliser une autre base de données pour les tests, le contexte de base de données de l’application doit être remplacé dans builder.ConfigureServices
. Pour plus d’informations, consultez la section Personnaliser WebApplicationFactory .
Ressources supplémentaires
Cette rubrique suppose une compréhension de base des tests unitaires. Si vous ne connaissez pas les concepts de test, consultez la rubrique Tests unitaires dans .NET Core et .NET Standard et son contenu lié.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
L’exemple d’application est une application Pages Razor et suppose une compréhension de base de Pages Razor. Si vous ne connaissez pas les Pages Razor, consultez les rubriques suivantes :
Remarque
Pour tester les SPA, nous vous recommandons d’utiliser un outil tel que Playwright for .NET, qui peut automatiser un navigateur.
Présentation des tests d’intégration
Les tests d’intégration évaluent les composants d’une application à un niveau plus large que les tests unitaires. Les tests unitaires sont utilisés pour tester des composants logiciels isolés, tels que des méthodes de classe individuelles. Les tests d’intégration confirment que deux ou plusieurs composants d’application fonctionnent ensemble pour produire un résultat attendu, y compris éventuellement chaque composant requis pour traiter entièrement une requête.
Ces tests plus larges sont utilisés pour tester l’infrastructure et la structure complète de l’application, y compris souvent les composants suivants :
- Base de données
- Système de fichiers
- Appliances réseau
- Pipeline requête-réponse
Les tests unitaires utilisent des composants fabriqués, connus comme des faux ou des objets fictifs, à la place des composants d’infrastructure.
En opposition aux tests unitaires, les tests d’intégration :
- Utilisent les composants réels que l’application utilise en production.
- Exigent davantage de code et de traitement des données.
- Prennent plus de temps pour s’exécuter.
Par conséquent, limitez l’utilisation des tests d’intégration aux scénarios d’infrastructure les plus importants. Si un comportement peut être testé à l’aide d’un test unitaire ou d’un test d’intégration, choisissez le test unitaire.
Dans les discussions sur les tests d’intégration, le projet testé est fréquemment appelé système testé, ou « ST » pour faire court. « ST » est utilisé tout au long de cet article pour faire référence à l’application ASP.NET Core testée.
N’écrivez pas de tests d’intégration pour chaque permutation des données et de l’accès aux fichiers avec des bases de données et des systèmes de fichiers. Quel que soit le nombre d’emplacements d’une application qui interagissent avec des bases de données et des systèmes de fichiers, un ensemble ciblé de tests d’intégration de lecture, d’écriture, de mise à jour et de suppression est généralement capable de tester de manière adéquate les composants de base de données et de système de fichiers. Utilisez des tests unitaires pour les tests de routine de la logique de méthode qui interagissent avec ces composants. Dans les tests unitaires, l’utilisation d’infrastructures fausses ou fictives entraîne une exécution des tests plus rapide.
tests d’intégration ASP.NET Core
Les tests d’intégration dans ASP.NET Core nécessitent les éléments suivants :
- Un projet de test est utilisé pour contenir et exécuter les tests. Le projet de test a une référence au ST.
- Le projet de test crée un hôte web de test pour le ST et utilise un client de serveur de test pour gérer les demandes et les réponses avec le ST.
- Un exécuteur de test est utilisé pour exécuter les tests et livrer les résultats des tests.
Les tests d’intégration suivent une séquence d’événements qui inclut les étapes de test habituelles Arrange, Act et Assert :
- L’hôte web du ST est configuré.
- Un client de serveur de test est créé pour envoyer des requêtes à l’application.
- L’étape de test Arrange est exécutée : l’application de test prépare une requête.
- L’étape de test Act est exécutée : le client envoie la requête et reçoit la réponse.
- L’étape de test Assert est exécutée : la réponse réelle est validée en tant que Réussite ou Échec en fonction d’une réponse attendue.
- Le processus se poursuit jusqu’à ce que tous les tests soient exécutés.
- Les résultats des tests sont livrés.
En règle générale, l’hôte web de test est configuré différemment de l’hôte web normal de l’application pour les séries de tests. Par exemple, une base de données différente ou des paramètres d’application différents peuvent être utilisés pour les tests.
Les composants d’infrastructure, tels que l’hôte web de test et le serveur de test en mémoire (TestServer), sont fournis ou gérés par le package Microsoft.AspNetCore.Mvc.Testing . L’utilisation de ce package simplifie la création et l’exécution des tests.
Le package Microsoft.AspNetCore.Mvc.Testing
gère les tâches suivantes :
- Copie le fichier de dépendances (
.deps
) du ST dans le répertoire du projet de testbin
. - Définit la racine du contenu sur la racine du projet du ST afin que soient trouvés les pages/vues et fichiers statiques quand les tests sont exécutés.
- Il fournit la classe WebApplicationFactory afin de simplifier l’amorçage de l’application testée avec
TestServer
.
La documentation sur les tests unitaires décrit comment configurer un projet de test et un exécuteur de tests, ainsi que des instructions détaillées sur l’exécution des tests et des recommandations pour nommer les tests et les classes de test.
Séparez les tests unitaires des tests d’intégration dans différents projets. Séparation des tests :
- Permet de s’assurer que les composants de test d’infrastructure ne sont pas accidentellement inclus dans les tests unitaires.
- Permet de contrôler quel ensemble de tests sont exécutés.
Il n’existe pratiquement aucune différence entre la configuration des tests des applications Pages Razor et des applications MVC. La seule différence réside dans la façon dont les tests sont nommés. Dans une application Pages Razor, les tests des points de terminaison de page sont généralement nommés d’après la classe de modèle de page (par exemple, IndexPageTests
pour tester l’intégration des composants pour la page Index). Dans une application MVC, les tests sont généralement organisés par classes de contrôleur et nommés d’après les contrôleurs qu’ils testent (par exemple, HomeControllerTests
pour tester l’intégration des composants pour le Home contrôleur).
Conditions préalables pour tester l’application
Le projet de test doit :
- Référencez le package
Microsoft.AspNetCore.Mvc.Testing
. - Spécifier le SDK Web dans le fichier projet (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Ces prérequis sont visibles dans l’échantillon d’application. Examinez le fichier tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
. L’échantillon d’application utilise l’infrastructure de test xUnit et la bibliothèque d’analyseur AngleSharp, de sorte que l’échantillon d’application référence également :
Dans les applications qui utilisent xunit.runner.visualstudio
la version 2.4.2 ou ultérieure, le projet de test doit référencer le package Microsoft.NET.Test.Sdk
.
Entity Framework Core est également utilisé dans les tests. L’application référence :
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory
Microsoft.EntityFrameworkCore.Tools
Environnement de ST
Si l’environnement de ST n’est pas paramétré, l’environnement est défini par défaut sur Développement.
Tests de base avec webApplicationFactory par défaut
WebApplicationFactory<TEntryPoint> est utilisé pour créer un TestServer pour les tests d’intégration. TEntryPoint
est la classe de point d’entrée du ST, généralement la classe Startup
.
Les classes de test implémentent une interface de fixture de classe (IClassFixture
) pour indiquer que la classe contient des tests et fournir des instances d’objets partagés entre les tests de la classe .
La classe de test suivante, BasicTests
, utilise WebApplicationFactory
pour démarrer le ST et fournir un HttpClient à une méthode de test, Get_EndpointsReturnSuccessAndCorrectContentType
. La méthode vérifie si le code d’état de la réponse est correct (codes d’état dans la plage 200-299) et si l’en-tête Content-Type
concerne text/html; charset=utf-8
pour plusieurs pages d’application.
CreateClient() crée une instance de HttpClient
qui suit automatiquement les redirections et gère les cookies.
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());
}
}
Par défaut, les cookies non essentiels ne sont pas conservés entre les requêtes lorsque la politique de consentement RGPD est activée. Pour conserver les cookies non essentiels , tels que ceux utilisés par le fournisseur TempData, marquez-les comme essentiels dans vos tests. Pour obtenir des instructions comment identifier un cookie comme essentiel, consultez Cookies essentiels.
Personnaliser WebApplicationFactory
La configuration de l’hôte web peut être créée indépendamment des classes de test en héritant de WebApplicationFactory
pour créer une ou plusieurs fabriques personnalisées :
Hériter de
WebApplicationFactory
et remplacer la méthode ConfigureWebHost. IWebHostBuilder autorise la configuration de la collection de services avec 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); } } }); } }
L’amorçage de base de données dans l’échantillon d’application est effectué par la méthode
InitializeDbForTests
. La méthode est décrite dans la section Échantillon de tests d’intégration : organisation de test d’application.Le contexte de base de données du ST est inscrit dans sa méthode
Startup.ConfigureServices
. Le rappel de l’application de testbuilder.ConfigureServices
est exécuté après l’exécution du code de l’applicationStartup.ConfigureServices
. L’ordre d’exécution est un changement cassant pour l’hôte générique avec la publication de ASP.NET Core 3.0. Pour utiliser une base de données différente pour les tests de la base de données de l’application, le contexte de base de données de l’application doit être remplacé dansbuilder.ConfigureServices
.Pour les ST qui utilisent toujours l’hôte web, le rappel de l’application de test
builder.ConfigureServices
est exécuté avant le code du STStartup.ConfigureServices
. Le rappel de l’application de testbuilder.ConfigureTestServices
est exécuté après.L’échantillon d’application trouve le descripteur de service pour le contexte de base de données et utilise le descripteur pour supprimer l’inscription du service. Ensuite, la fabrique ajoute un nouveau
ApplicationDbContext
qui utilise une base de données en mémoire pour les tests.Pour vous connecter à une base de données différente de la base de données en mémoire, modifiez l’appel
UseInMemoryDatabase
pour connecter le contexte à une autre base de données. Pour utiliser une base de données de test SQL Server :- Référencez le package NuGet
Microsoft.EntityFrameworkCore.SqlServer
dans le fichier projet. - Appelez
UseSqlServer
avec une chaîne de connexion à la base de données.
services.AddDbContext<ApplicationDbContext>((options, context) => { context.UseSqlServer( Configuration.GetConnectionString("TestingDbConnectionString")); });
- Référencez le package NuGet
Utilisez le
CustomWebApplicationFactory
personnalisé dans les classes de test. L’exemple suivant utilise la fabrique dans la classeIndexPageTests
: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 }); }
Le client de l’échantillon d’application est configuré pour empêcher le
HttpClient
d’effectuer les redirections suivantes. Comme expliqué plus loin dans la section Authentification fictive, cela permet aux tests de vérifier le résultat de la première réponse de l’application. La première réponse est une redirection dans la plupart de ces tests avec un en-têteLocation
.Un test classique utilise le
HttpClient
et les méthodes d’assistance pour traiter la requête et la réponse :[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); }
Toute requête POST adressée au ST doit satisfaire les contrôles antifalsification effectués automatiquement par le système antifalsification de protection des données de l’application. Pour organiser la requête POST d’un test, l’application de test doit :
- Effectuer une requête pour la page.
- Analyser l’antifalsification cookie et demander le jeton de validation à partir de la réponse.
- Effectuer la requête POST avec l’antifalsification cookie et le jeton de validation de la requête en place.
Les méthodes d’extension d’assistance SendAsync
(Helpers/HttpClientExtensions.cs
) et la méthode d’assistance GetDocumentAsync
(Helpers/HtmlHelpers.cs
) dans l’échantillon d’application utilisent l’analyseur AngleSharp pour gérer les contrôles antifalsification avec les méthodes suivantes :
GetDocumentAsync
: Reçoit le HttpResponseMessage et retourne unIHtmlDocument
.GetDocumentAsync
utilise une fabrique qui prépare une réponse virtuelle basée sur l’originalHttpResponseMessage
. Pour plus d’informations, consultez la documentation AngleSharp.- Les méthodes d’extension
SendAsync
pour leHttpClient
composent un HttpRequestMessage et appellent SendAsync(HttpRequestMessage) pour envoyer des demandes au ST. Les surcharges pourSendAsync
acceptent le formulaire HTML (IHtmlFormElement
) et les éléments suivants :- Bouton Envoyer du formulaire (
IHtmlElement
) - Collection de valeurs de formulaire (
IEnumerable<KeyValuePair<string, string>>
) - Bouton Envoyer (
IHtmlElement
) et valeurs de formulaire (IEnumerable<KeyValuePair<string, string>>
)
- Bouton Envoyer du formulaire (
Remarque
AngleSharp est une bibliothèque d’analyse tierce utilisée à des fins de démonstration dans cette rubrique et dans l’échantillon d’application. AngleSharp n’est pas pris en charge ou requis pour les tests d’intégration des applications ASP.NET Core. D’autres analyseurs peuvent être utilisés, tels que le Html Agility Pack (HAP). Une autre approche consiste à écrire du code pour gérer directement l’antifalsification et le jeton de vérification de requête du système antifalsification cookie.
Remarque
Le fournisseur de base de données en mémoire EF-Core peut être utilisé pour des tests limités et de base, mais le fournisseur SQLite est le choix recommandé pour les tests en mémoire.
Personnaliser le client avec WithWebHostBuilder
Quand une configuration supplémentaire est requise dans une méthode de test, WithWebHostBuilder crée un WebApplicationFactory
avec un IWebHostBuilder qui est davantage personnalisé par la configuration.
La Post_DeleteMessageHandler_ReturnsRedirectToRoot
méthode de test de l’échantillon d’application illustre l’utilisation de WithWebHostBuilder
. Ce test effectue une suppression d’enregistrement dans la base de données en déclenchant une soumission de formulaire dans le ST.
Étant donné qu’un autre test de la classe IndexPageTests
effectue une opération qui supprime tous les enregistrements de la base de données et peut s’exécuter avant la méthode Post_DeleteMessageHandler_ReturnsRedirectToRoot
, la base de données est réamorcée dans cette méthode de test pour s’assurer qu’un enregistrement est présent pour que le ST puisse supprimer. Sélectionner le premier bouton de suppression du formulaire messages
dans le ST est simulé dans la requête adressée au ST :
[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);
}
Options du client
Le tableau suivant montre la valeur par défaut WebApplicationFactoryClientOptions disponible lors de la création d’instances HttpClient
.
Option | Description | Default |
---|---|---|
AllowAutoRedirect | Obtient ou définit, si les instances HttpClient doivent ou non suivre automatiquement les réponses de redirection. |
true |
BaseAddress | Obtient ou définit l’adresse de base des instances HttpClient . |
http://localhost |
HandleCookies | Obtient ou définit si les instances HttpClient doivent gérer les cookies. |
true |
MaxAutomaticRedirections | Obtient ou définit le nombre maximal de réponses de redirection que les instances HttpClient doivent suivre. |
7 |
Créez la classe WebApplicationFactoryClientOptions
et passez-la à la méthode CreateClient() (les valeurs par défaut sont indiquées dans l’exemple de code) :
// 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);
Injecter des services fictifs
Les services peuvent être remplacés dans un test avec un appel à ConfigureTestServices sur le générateur d’hôte. Pour injecter des services fictifs, le ST doit avoir une classe Startup
avec une méthode Startup.ConfigureServices
.
L’échantillon de ST inclut un service délimité qui retourne un devis. Le devis est incorporé dans un champ masqué de la page Index lorsque la page Index est demandée.
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">
Le balisage suivant est généré lors de l’exécution de l’application ST :
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Pour tester le service et l’injection de devis dans un test d’intégration, un service fictif est injecté dans le ST par le test. Le service fictif remplace le QuoteService
de l’application par un service fourni par l’application de test, appelé 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
est appelé et le service délimité est inscrit :
[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);
}
Le balisage produit pendant l’exécution du test reflète le texte de guillemet fourni par TestQuoteService
, ainsi l’assertion aboutit :
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Authentification fictive
Les tests dans la classe AuthTests
vérifient qu’un point de terminaison sécurisé :
- Redirige un utilisateur non authentifié vers la page de connexion de l’application.
- Retourne le contenu d’un utilisateur authentifié.
Dans le ST, la page /SecurePage
utilise une convention AuthorizePage pour appliquer un AuthorizeFilter à la page. Pour plus d’informations sur les conventions, consultez Conventions des autorisations de Pages Razor.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Dans le test Get_SecurePageRedirectsAnUnauthenticatedUser
, un WebApplicationFactoryClientOptions est défini pour interdire les redirections en définissant AllowAutoRedirect sur 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);
}
En interdisant au client de suivre la redirection, les vérifications suivantes peuvent être effectuées :
- Le code d’état retourné par le ST peut être vérifié par rapport au résultat attendu HttpStatusCode.Redirect, et non par rapport au code d’état final après la redirection vers la page de connexion, qui serait HttpStatusCode.OK.
- La valeur d’en-tête
Location
dans les en-têtes de réponse est vérifiée pour confirmer qu’elle commence parhttp://localhost/Identity/Account/Login
, et non par la réponse de la page de connexion finale, où l’en-têteLocation
ne serait pas présent.
L’application de test peut simuler un AuthenticationHandler<TOptions> dans ConfigureTestServices afin de tester des aspects de l’authentification et de l’autorisation. Un scénario minimal retourne un 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);
}
}
TestAuthHandler
est appelé pour authentifier un utilisateur lorsque le schéma d’authentification est défini sur Test
quand AddAuthentication
est inscrit pour ConfigureTestServices
. Il est important que le schéma Test
corresponde à celui attendu par votre application. Sinon, l’authentification ne fonctionnera pas.
[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);
}
Pour plus d’informations sur WebApplicationFactoryClientOptions
, consultez la section Options du client.
Définir l’environnement
Par défaut, l’environnement hôte et l’environnement d’application du ST sont configurés pour utiliser l’environnement de développement. Pour remplacer l’environnement du ST lors de l’utilisation de IHostBuilder
:
- Définissez la variable d’environnement
ASPNETCORE_ENVIRONMENT
(par exemple,Staging
,Production
ou une autre valeur personnalisée, telle queTesting
). - Remplacez
CreateHostBuilder
dans l’application de test pour lire les variables d’environnement précédées deASPNETCORE
.
protected override IHostBuilder CreateHostBuilder() =>
base.CreateHostBuilder()
.ConfigureHostConfiguration(
config => config.AddEnvironmentVariables("ASPNETCORE"));
Si le ST utilise l’hôte web (IWebHostBuilder
), remplacez CreateWebHostBuilder
:
protected override IWebHostBuilder CreateWebHostBuilder() =>
base.CreateWebHostBuilder().UseEnvironment("Testing");
Comment l’infrastructure de test déduit le chemin racine du contenu de l’application
Le constructeur WebApplicationFactory
déduit le chemin racine du contenu de l’application en recherchant un WebApplicationFactoryContentRootAttribute sur l’assembly contenant les tests d’intégration avec une clé égale à TEntryPoint
l’assembly System.Reflection.Assembly.FullName
. Si un attribut avec la clé correcte est introuvable, WebApplicationFactory
revient à rechercher un fichier de solution (.sln) et ajoute le nom de l’assembly TEntryPoint
au répertoire de la solution. Le répertoire racine de l’application (le chemin racine du contenu) est utilisé pour découvrir les vues et les fichiers de contenu.
Désactiver le cliché instantané
Le cliché instantané entraîne l’exécution des tests dans un répertoire différent du répertoire de sortie. Si vos tests reposent sur le chargement de fichiers relatifs à Assembly.Location
et que vous rencontrez des problèmes, vous devrez peut-être désactiver le cliché instantané.
Pour désactiver le cliché instantané lors de l’utilisation de xUnit, créez un fichier xunit.runner.json
dans le répertoire de votre projet de test, avec le paramètre de configuration correct :
{
"shadowCopy": false
}
Élimination des objets
Une fois les tests de l’implémentation IClassFixture
exécutés, TestServer et HttpClient sont supprimés lorsque xUnit supprime le WebApplicationFactory
. Si les objets instanciés par le développeur doivent être éliminés, éliminez-les dans l’implémentation IClassFixture
. Pour plus d’informations, consultez Implémentation d’une méthode Dispose.
Exemple de tests d’intégration
L’échantillon d’application est composé de deux applications :
App | Répertoire du projet | Description |
---|---|---|
Application de message (ST) | src/RazorPagesProject |
Permet à un utilisateur d’ajouter, de supprimer un message, de tout supprimer et d’analyser les messages. |
Tester une application | tests/RazorPagesProject.Tests |
Utilisé pour tester l’intégration du ST. |
Les tests peuvent être exécutés à l’aide des fonctionnalités de test intégrées d’un environnement de développement intégré, telles que Visual Studio. Si vous utilisez Visual Studio Code ou la ligne de commande, exécutez la commande suivante à une invite de commandes dans le répertoire tests/RazorPagesProject.Tests
:
dotnet test
Organisation d’application de message (ST)
Le ST est un système de messages Pages Razor avec les caractéristiques suivantes :
- La page Index de l’application (
Pages/Index.cshtml
etPages/Index.cshtml.cs
) fournit une interface utilisateur et des méthodes de modèle de page pour contrôler l’ajout, la suppression et l’analyse des messages (mots moyens par message). - Un message est décrit par la classe
Message
(Data/Message.cs
) avec deux propriétés :Id
(clé) etText
(message). La propriétéText
est obligatoire et limitée à 200 caractères. - Les messages sont stockés à l’aide de la base de données en mémoire d’Entity Framework†.
- L’application contient une couche d’accès aux données dans sa classe de contexte de base de données,
AppDbContext
(Data/AppDbContext.cs
). - Si la base de données est vide au démarrage de l’application, la banque de messages est initialisée avec trois messages.
- L’application inclut un
/SecurePage
qui n’est accessible qu’à un utilisateur authentifié.
†La rubrique EF, Tester avec InMemory, explique comment utiliser une base de données en mémoire pour les tests avec MSTest. Cette rubrique utilise l’infrastructure de test xUnit. Les concepts de test et les implémentations de test entre différents frameworks de test sont similaires, mais pas identiques.
Bien que l’application n’utilise pas le modèle de référentiel et ne soit pas un exemple efficace du modèle d’unité de travail (UoW), Razor Pages prend en charge ces modèles de développement. Pour plus d’informations, consultez Conception de la couche de persistance de l’infrastructure et Tester la logique du contrôleur (l’échantillon implémente le modèle de référentiel).
Tester l’organisation de l’application
L’application de test est une application console à l’intérieur du répertoire tests/RazorPagesProject.Tests
.
Répertoire de l’application de test | Description |
---|---|
AuthTests |
Contient des méthodes de test pour :
|
BasicTests |
Contient une méthode de test pour le routage et le type de contenu. |
IntegrationTests |
Contient les tests d’intégration pour la page Index à l’aide de la classe personnalisée WebApplicationFactory . |
Helpers/Utilities |
|
Le framework de test est xUnit. Les tests d’intégration sont effectués à l’aide de Microsoft.AspNetCore.TestHost, qui inclut le TestServer. Étant donné que le package Microsoft.AspNetCore.Mvc.Testing
est utilisé pour configurer l’hôte de test et le serveur de test, les packages TestHost
et TestServer
ne nécessitent pas de références de package directes dans le fichier projet de l’application de test ou la configuration du développeur dans l’application de test.
Les tests d’intégration nécessitent généralement un petit jeu de données dans la base de données avant l’exécution du test. Par exemple, un test de suppression appelle une suppression d’enregistrement de base de données, de sorte que la base de données doit avoir au moins un enregistrement pour que la requête de suppression aboutisse.
L’échantillon d’application amorcera la base de données avec trois messages dans Utilities.cs
que les tests peuvent utiliser lorsqu’ils s’exécutent :
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." }
};
}
Le contexte de base de données du ST est inscrit dans sa méthode Startup.ConfigureServices
. Le rappel de l’application de test builder.ConfigureServices
est exécuté après l’exécution du code de l’application Startup.ConfigureServices
. Pour utiliser une autre base de données pour les tests, le contexte de base de données de l’application doit être remplacé dans builder.ConfigureServices
. Pour plus d’informations, consultez la section Personnaliser WebApplicationFactory .
Pour les ST qui utilisent toujours l’hôte web, le rappel de l’application de test builder.ConfigureServices
est exécuté avant le code du ST Startup.ConfigureServices
. Le rappel de l’application de test builder.ConfigureTestServices
est exécuté après.
Ressources supplémentaires
Cet article suppose une connaissance élémentaire des tests unitaires. Si vous ne connaissez pas les concepts de test, consultez l’article Tests unitaires dans .NET Core et .NET Standard et son contenu lié.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
L’exemple d’application est une application Pages Razor et suppose une compréhension de base de Pages Razor. Si vous n’êtes pas familiarisé avec Pages Razor, consultez les articles suivants :
Pour tester les SPA, nous vous recommandons d’utiliser un outil tel que Playwright pour .NET, qui peut automatiser un navigateur.
Présentation des tests d’intégration
Les tests d’intégration évaluent les composants d’une application à un niveau plus large que les tests unitaires. Les tests unitaires sont utilisés pour tester des composants logiciels isolés, tels que des méthodes de classe individuelles. Les tests d’intégration confirment que deux ou plusieurs composants d’application fonctionnent ensemble pour produire un résultat attendu, y compris éventuellement chaque composant requis pour traiter entièrement une requête.
Ces tests plus larges sont utilisés pour tester l’infrastructure et la structure complète de l’application, y compris souvent les composants suivants :
- Base de données
- Système de fichiers
- Appliances réseau
- Pipeline requête-réponse
Les tests unitaires utilisent des composants fabriqués, connus comme des faux ou des objets fictifs, à la place des composants d’infrastructure.
En opposition aux tests unitaires, les tests d’intégration :
- Utilisent les composants réels que l’application utilise en production.
- Exigent davantage de code et de traitement des données.
- Prennent plus de temps pour s’exécuter.
Par conséquent, limitez l’utilisation des tests d’intégration aux scénarios d’infrastructure les plus importants. Si un comportement peut être testé à l’aide d’un test unitaire ou d’un test d’intégration, choisissez le test unitaire.
Dans les discussions sur les tests d’intégration, le projet testé est fréquemment appelé système testé, ou « ST » pour faire court. « ST » est utilisé tout au long de cet article pour faire référence à l’application ASP.NET Core testée.
N’écrivez pas de tests d’intégration pour chaque permutation des données et de l’accès aux fichiers avec des bases de données et des systèmes de fichiers. Quel que soit le nombre d’emplacements d’une application qui interagissent avec des bases de données et des systèmes de fichiers, un ensemble ciblé de tests d’intégration de lecture, d’écriture, de mise à jour et de suppression est généralement capable de tester de manière adéquate les composants de base de données et de système de fichiers. Utilisez des tests unitaires pour les tests de routine de la logique de méthode qui interagissent avec ces composants. Dans les tests unitaires, l’utilisation d’infrastructures fausses ou fictives entraîne une exécution des tests plus rapide.
tests d’intégration ASP.NET Core
Les tests d’intégration dans ASP.NET Core nécessitent les éléments suivants :
- Un projet de test est utilisé pour contenir et exécuter les tests. Le projet de test a une référence au ST.
- Le projet de test crée un hôte web de test pour le ST et utilise un client de serveur de test pour gérer les demandes et les réponses avec le ST.
- Un exécuteur de test est utilisé pour exécuter les tests et livrer les résultats des tests.
Les tests d’intégration suivent une séquence d’événements qui inclut les étapes de test habituelles Arrange, Act et Assert :
- L’hôte web du ST est configuré.
- Un client de serveur de test est créé pour envoyer des requêtes à l’application.
- L’étape de test Arrange est exécutée : l’application de test prépare une requête.
- L’étape de test Act est exécutée : le client envoie la requête et reçoit la réponse.
- L’étape de test Assert est exécutée : la réponse réelle est validée en tant que Réussite ou Échec en fonction d’une réponse attendue.
- Le processus se poursuit jusqu’à ce que tous les tests soient exécutés.
- Les résultats des tests sont livrés.
En règle générale, l’hôte web de test est configuré différemment de l’hôte web normal de l’application pour les séries de tests. Par exemple, une base de données différente ou des paramètres d’application différents peuvent être utilisés pour les tests.
Les composants d’infrastructure, tels que l’hôte web de test et le serveur de test en mémoire (TestServer), sont fournis ou gérés par le package Microsoft.AspNetCore.Mvc.Testing . L’utilisation de ce package simplifie la création et l’exécution des tests.
Le package Microsoft.AspNetCore.Mvc.Testing
gère les tâches suivantes :
- Copie le fichier de dépendances (
.deps
) du ST dans le répertoire du projet de testbin
. - Définit la racine du contenu sur la racine du projet du ST afin que soient trouvés les pages/vues et fichiers statiques quand les tests sont exécutés.
- Il fournit la classe WebApplicationFactory afin de simplifier l’amorçage de l’application testée avec
TestServer
.
La documentation sur les tests unitaires décrit comment configurer un projet de test et un exécuteur de tests, ainsi que des instructions détaillées sur l’exécution des tests et des recommandations pour nommer les tests et les classes de test.
Séparez les tests unitaires des tests d’intégration dans différents projets. Séparation des tests :
- Permet de s’assurer que les composants de test d’infrastructure ne sont pas accidentellement inclus dans les tests unitaires.
- Permet de contrôler quel ensemble de tests sont exécutés.
Il n’existe pratiquement aucune différence entre la configuration des tests des applications Pages Razor et des applications MVC. La seule différence réside dans la façon dont les tests sont nommés. Dans une application Pages Razor, les tests des points de terminaison de page sont généralement nommés d’après la classe de modèle de page (par exemple, IndexPageTests
pour tester l’intégration des composants pour la page Index). Dans une application MVC, les tests sont généralement organisés par classes de contrôleur et nommés d’après les contrôleurs qu’ils testent (par exemple, HomeControllerTests
pour tester l’intégration des composants pour le Home contrôleur).
Conditions préalables pour tester l’application
Le projet de test doit :
- Référencez le package
Microsoft.AspNetCore.Mvc.Testing
. - Spécifier le SDK Web dans le fichier projet (
<Project Sdk="Microsoft.NET.Sdk.Web">
).
Ces prérequis sont visibles dans l’échantillon d’application. Examinez le fichier tests/RazorPagesProject.Tests/RazorPagesProject.Tests.csproj
. L’échantillon d’application utilise l’infrastructure de test xUnit et la bibliothèque d’analyseur AngleSharp, de sorte que l’échantillon d’application référence également :
Dans les applications qui utilisent xunit.runner.visualstudio
la version 2.4.2 ou ultérieure, le projet de test doit référencer le package Microsoft.NET.Test.Sdk
.
Entity Framework Core est également utilisé dans les tests. Consultez le fichier projet dans GitHub.
Environnement de ST
Si l’environnement de ST n’est pas paramétré, l’environnement est défini par défaut sur Développement.
Tests de base avec webApplicationFactory par défaut
Exposez la classe Program
implicitement définie au projet de test en effectuant l’une des opérations suivantes :
Exposez les types internes de l’application web au projet de test. Cette opération peut être effectuée dans le fichier du projet de ST (
.csproj
) :<ItemGroup> <InternalsVisibleTo Include="MyTestProject" /> </ItemGroup>
Rendez la classe
Program
publique à l’aide d’une déclaration de classe partielle :var builder = WebApplication.CreateBuilder(args); // ... Configure services, routes, etc. app.Run(); + public partial class Program { }
L’échantillon d’application utilise l’approche de classe partielle
Program
.
WebApplicationFactory<TEntryPoint> est utilisé pour créer un TestServer pour les tests d’intégration. TEntryPoint
est la classe de point d’entrée du ST, généralement Program.cs
.
Les classes de test implémentent une interface de fixture de classe (IClassFixture
) pour indiquer que la classe contient des tests et fournir des instances d’objets partagés entre les tests de la classe .
La classe de test suivante, BasicTests
, utilise WebApplicationFactory
pour démarrer le ST et fournir un HttpClient à une méthode de test, Get_EndpointsReturnSuccessAndCorrectContentType
. La méthode vérifie que le code d’état de la réponse a réussi (200-299) et que l’en-tête Content-Type
est text/html; charset=utf-8
sur plusieurs pages d’application.
CreateClient() crée une instance de HttpClient
qui suit automatiquement les redirections et gère les cookies.
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());
}
}
Par défaut, les cookies non essentiels ne sont pas conservés entre les requêtes lorsque la politique de consentement du Règlement général sur la protection des données est activée. Pour conserver les cookies non essentiels , tels que ceux utilisés par le fournisseur TempData, marquez-les comme essentiels dans vos tests. Pour obtenir des instructions comment identifier un cookie comme essentiel, consultez Cookies essentiels.
AngleSharp vs Application Parts
pour les contrôles antifalsification
Cet article utilise l’analyseur AngleSharp pour gérer les vérifications antifalsification en chargeant des pages et en analysant le code HTML. Pour tester les points de terminaison des vues contrôleur et Pages Razor à un niveau inférieur, sans vous soucier de la façon dont ils s’affichent dans le navigateur, pensez à utiliser Application Parts
. L’approche des composants d’application injecte un contrôleur ou une page Razor dans l’application qui peut être utilisé pour effectuer des requêtes JSON afin d’obtenir les valeurs requises. Pour plus d’informations, consultez le blog Integration Testing ASP.NET Core Resources Protected with Antiforgery Using Application Parts et le référentiel GitHub associé par Martin Costello.
Personnaliser WebApplicationFactory
La configuration de l’hôte web peut être créée indépendamment des classes de test en héritant de WebApplicationFactory<TEntryPoint> pour créer une ou plusieurs fabriques personnalisées :
Hériter de
WebApplicationFactory
et remplacer la méthode ConfigureWebHost. IWebHostBuilder autorise la configuration de la collection de services avecIWebHostBuilder.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"); } }
L’amorçage de base de données dans l’échantillon d’application est effectué par la méthode
InitializeDbForTests
. La méthode est décrite dans la section Échantillon de tests d’intégration : organisation de test d’application.Le contexte de base de données du ST est inscrit dans
Program.cs
. Le rappel de l’application de testbuilder.ConfigureServices
est exécuté après l’exécution du code de l’applicationProgram.cs
. Pour utiliser une base de données différente pour les tests de la base de données de l’application, le contexte de base de données de l’application doit être remplacé dansbuilder.ConfigureServices
.L’échantillon d’application trouve le descripteur de service pour le contexte de base de données et utilise le descripteur pour supprimer l’inscription du service. La fabrique ajoute ensuite un nouveau
ApplicationDbContext
qui utilise une base de données en mémoire pour les tests.Pour vous connecter à une autre base de données, modifiez le
DbConnection
. Pour utiliser une base de données de test SQL Server :
- Référencez le package NuGet
Microsoft.EntityFrameworkCore.SqlServer
dans le fichier projet. - Appelez
UseInMemoryDatabase
.
Utilisez le
CustomWebApplicationFactory
personnalisé dans les classes de test. L’exemple suivant utilise la fabrique dans la classeIndexPageTests
: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 }); }
Le client de l’échantillon d’application est configuré pour empêcher le
HttpClient
d’effectuer les redirections suivantes. Comme expliqué plus loin dans la section Authentification fictive, cela permet aux tests de vérifier le résultat de la première réponse de l’application. La première réponse est une redirection dans la plupart de ces tests avec un en-têteLocation
.Un test classique utilise le
HttpClient
et les méthodes d’assistance pour traiter la requête et la réponse :[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); }
Toute requête POST adressée au ST doit satisfaire les contrôles antifalsification effectués automatiquement par le système antifalsification de protection des données de l’application. Pour organiser la requête POST d’un test, l’application de test doit :
- Effectuer une requête pour la page.
- Analyser l’antifalsification cookie et demander le jeton de validation à partir de la réponse.
- Effectuer la requête POST avec l’antifalsification cookie et le jeton de validation de la requête en place.
Les méthodes d’extension d’assistance SendAsync
(Helpers/HttpClientExtensions.cs
) et la méthode d’assistance GetDocumentAsync
(Helpers/HtmlHelpers.cs
) dans l’échantillon d’application utilisent l’analyseur AngleSharp pour gérer les contrôles antifalsification avec les méthodes suivantes :
GetDocumentAsync
: Reçoit le HttpResponseMessage et retourne unIHtmlDocument
.GetDocumentAsync
utilise une fabrique qui prépare une réponse virtuelle basée sur l’originalHttpResponseMessage
. Pour plus d’informations, consultez la documentation AngleSharp.- Les méthodes d’extension
SendAsync
pour leHttpClient
composent un HttpRequestMessage et appellent SendAsync(HttpRequestMessage) pour envoyer des demandes au ST. Les surcharges pourSendAsync
acceptent le formulaire HTML (IHtmlFormElement
) et les éléments suivants :- Bouton Envoyer du formulaire (
IHtmlElement
) - Collection de valeurs de formulaire (
IEnumerable<KeyValuePair<string, string>>
) - Bouton Envoyer (
IHtmlElement
) et valeurs de formulaire (IEnumerable<KeyValuePair<string, string>>
)
- Bouton Envoyer du formulaire (
AngleSharp est une bibliothèque d’analyse tierce utilisée à des fins de démonstration dans cet article et dans l’échantillon d’application. AngleSharp n’est pas pris en charge ou requis pour les tests d’intégration des applications ASP.NET Core. D’autres analyseurs peuvent être utilisés, tels que le Html Agility Pack (HAP). Une autre approche consiste à écrire du code pour gérer directement l’antifalsification et le jeton de vérification de requête du système antifalsification cookie. Pour plus d’informations, consultez AngleSharp vs Application Parts
pour les contrôles antifalsification dans cet article.
Le fournisseur de base de données en mémoire EF-Core peut être utilisé pour des tests limités et de base, mais le fournisseur SQLite est le choix recommandé pour les tests en mémoire.
Consultez Étendre le démarrage avec des filtres de démarrage qui montre comment configurer l’intergiciel à l’aide de IStartupFilter, ce qui est utile lorsqu’un test nécessite un service ou un intergiciel personnalisé.
Personnaliser le client avec WithWebHostBuilder
Quand une configuration supplémentaire est requise dans une méthode de test, WithWebHostBuilder crée un WebApplicationFactory
avec un IWebHostBuilder qui est davantage personnalisé par la configuration.
L’exemple de code appelle WithWebHostBuilder
pour remplacer les services configurés par des stubs de test. Pour obtenir plus d’informations et pour des exemples d’utilisation, consultez Injecter des services fictifs dans cet article.
La Post_DeleteMessageHandler_ReturnsRedirectToRoot
méthode de test de l’échantillon d’application illustre l’utilisation de WithWebHostBuilder
. Ce test effectue une suppression d’enregistrement dans la base de données en déclenchant une soumission de formulaire dans le ST.
Étant donné qu’un autre test de la classe IndexPageTests
effectue une opération qui supprime tous les enregistrements de la base de données et peut s’exécuter avant la méthode Post_DeleteMessageHandler_ReturnsRedirectToRoot
, la base de données est réamorcée dans cette méthode de test pour s’assurer qu’un enregistrement est présent pour que le ST puisse supprimer. Sélectionner le premier bouton de suppression du formulaire messages
dans le ST est simulé dans la requête adressée au ST :
[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);
}
Options du client
Consultez la page WebApplicationFactoryClientOptions pour connaître les valeurs par défaut et les options disponibles lors de la création d’instances HttpClient
.
Créez la classe WebApplicationFactoryClientOptions
et passez-la à la méthode CreateClient() :
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
});
}
REMARQUE : Pour éviter les avertissements de redirection HTTPS dans les journaux d’activité, lors de l’utilisation d’intergiciel de redirection HTTPS, définissez BaseAddress = new Uri("https://localhost")
Injecter des services fictifs
Les services peuvent être remplacés dans un test avec un appel à ConfigureTestServices sur le générateur d’hôte. Pour étendre les services remplacés au test lui-même, la méthode WithWebHostBuilder est utilisée pour récupérer un générateur d’hôtes. Vous pouvez le voir dans les tests suivants :
- Get_QuoteService_ProvidesQuoteInPage
- Get_GithubProfilePageCanGetAGithubUser
- Get_SecurePageIsReturnedForAnAuthenticatedUser
L’échantillon de ST inclut un service délimité qui retourne un devis. Le devis est incorporé dans un champ masqué de la page Index lorsque la page Index est demandée.
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">
Le balisage suivant est généré lors de l’exécution de l’application ST :
<input id="quote" type="hidden" value="Come on, Sarah. We've an appointment in
London, and we're already 30,000 years late.">
Pour tester le service et l’injection de devis dans un test d’intégration, un service fictif est injecté dans le ST par le test. Le service fictif remplace le QuoteService
de l’application par un service fourni par l’application de test, appelé 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
est appelé et le service délimité est inscrit :
[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);
}
Le balisage produit pendant l’exécution du test reflète le texte de guillemet fourni par TestQuoteService
, ainsi l’assertion aboutit :
<input id="quote" type="hidden" value="Something's interfering with time,
Mr. Scarman, and time is my business.">
Authentification fictive
Les tests dans la classe AuthTests
vérifient qu’un point de terminaison sécurisé :
- Redirige un utilisateur non authentifié vers la page de connexion de l’application.
- Retourne le contenu d’un utilisateur authentifié.
Dans le ST, la page /SecurePage
utilise une convention AuthorizePage pour appliquer un AuthorizeFilter à la page. Pour plus d’informations sur les conventions, consultez Conventions des autorisations de Pages Razor.
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/SecurePage");
});
Dans le test Get_SecurePageRedirectsAnUnauthenticatedUser
, un WebApplicationFactoryClientOptions est défini pour interdire les redirections en définissant AllowAutoRedirect sur 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);
}
En interdisant au client de suivre la redirection, les vérifications suivantes peuvent être effectuées :
- Le code d’état retourné par le ST peut être vérifié par rapport au résultat HttpStatusCode.Redirect attendu, et non par rapport au code d’état final après la redirection vers la page de connexion, qui serait HttpStatusCode.OK.
- La valeur d’en-tête
Location
dans les en-têtes de réponse est vérifiée pour confirmer qu’elle commence parhttp://localhost/Identity/Account/Login
, et non par la réponse de la page de connexion finale, où l’en-têteLocation
ne serait pas présent.
L’application de test peut simuler un AuthenticationHandler<TOptions> dans ConfigureTestServices afin de tester des aspects de l’authentification et de l’autorisation. Un scénario minimal retourne un 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);
}
}
TestAuthHandler
est appelé pour authentifier un utilisateur lorsque le schéma d’authentification est défini sur TestScheme
quand AddAuthentication
est inscrit pour ConfigureTestServices
. Il est important que le schéma TestScheme
corresponde à celui attendu par votre application. Sinon, l’authentification ne fonctionnera pas.
[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);
}
Pour plus d’informations sur WebApplicationFactoryClientOptions
, consultez la section Options du client.
Tests de base pour l’intergiciel d’authentification
Consultez ce référentiel GitHub pour les tests de base de l’intergiciel d’authentification. Il contient un serveur de test spécifique au scénario de test.
Définir l’environnement
Définissez l’environnement dans la fabrique d’application personnalisée :
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");
}
}
Comment l’infrastructure de test déduit le chemin racine du contenu de l’application
Le constructeur WebApplicationFactory
déduit le chemin racine du contenu de l’application en recherchant un WebApplicationFactoryContentRootAttribute sur l’assembly contenant les tests d’intégration avec une clé égale à TEntryPoint
l’assembly System.Reflection.Assembly.FullName
. Si un attribut avec la clé correcte est introuvable, WebApplicationFactory
revient à rechercher un fichier de solution (.sln) et ajoute le nom de l’assembly TEntryPoint
au répertoire de la solution. Le répertoire racine de l’application (le chemin racine du contenu) est utilisé pour découvrir les vues et les fichiers de contenu.
Désactiver le cliché instantané
Le cliché instantané entraîne l’exécution des tests dans un répertoire différent du répertoire de sortie. Si vos tests reposent sur le chargement de fichiers relatifs à Assembly.Location
et que vous rencontrez des problèmes, vous devrez peut-être désactiver le cliché instantané.
Pour désactiver le cliché instantané lors de l’utilisation de xUnit, créez un fichier xunit.runner.json
dans le répertoire de votre projet de test, avec le paramètre de configuration correct :
{
"shadowCopy": false
}
Élimination des objets
Une fois les tests de l’implémentation IClassFixture
exécutés, TestServer et HttpClient sont supprimés lorsque xUnit supprime le WebApplicationFactory
. Si les objets instanciés par le développeur doivent être éliminés, éliminez-les dans l’implémentation IClassFixture
. Pour plus d’informations, consultez Implémentation d’une méthode Dispose.
Exemple de tests d’intégration
L’échantillon d’application est composé de deux applications :
App | Répertoire du projet | Description |
---|---|---|
Application de message (ST) | src/RazorPagesProject |
Permet à un utilisateur d’ajouter, de supprimer un message, de tout supprimer et d’analyser les messages. |
Tester une application | tests/RazorPagesProject.Tests |
Utilisé pour tester l’intégration du ST. |
Les tests peuvent être exécutés à l’aide des fonctionnalités de test intégrées d’un environnement de développement intégré, telles que Visual Studio. Si vous utilisez Visual Studio Code ou la ligne de commande, exécutez la commande suivante à une invite de commandes dans le répertoire tests/RazorPagesProject.Tests
:
dotnet test
Organisation d’application de message (ST)
Le ST est un système de messages Pages Razor avec les caractéristiques suivantes :
- La page Index de l’application (
Pages/Index.cshtml
etPages/Index.cshtml.cs
) fournit une interface utilisateur et des méthodes de modèle de page pour contrôler l’ajout, la suppression et l’analyse des messages (mots moyens par message). - Un message est décrit par la classe
Message
(Data/Message.cs
) avec deux propriétés :Id
(clé) etText
(message). La propriétéText
est obligatoire et limitée à 200 caractères. - Les messages sont stockés à l’aide de la base de données en mémoire d’Entity Framework†.
- L’application contient une couche d’accès aux données dans sa classe de contexte de base de données,
AppDbContext
(Data/AppDbContext.cs
). - Si la base de données est vide au démarrage de l’application, la banque de messages est initialisée avec trois messages.
- L’application inclut un
/SecurePage
qui n’est accessible qu’à un utilisateur authentifié.
†L’article EF, Tester avec InMemory, explique comment utiliser une base de données en mémoire pour les tests avec MSTest. Cette rubrique utilise l’infrastructure de test xUnit. Les concepts de test et les implémentations de test entre différents frameworks de test sont similaires, mais pas identiques.
Bien que l’application n’utilise pas le modèle de référentiel et ne soit pas un exemple efficace du modèle d’unité de travail (UoW), Razor Pages prend en charge ces modèles de développement. Pour plus d’informations, consultez Conception de la couche de persistance de l’infrastructure et Tester la logique du contrôleur (l’échantillon implémente le modèle de référentiel).
Tester l’organisation de l’application
L’application de test est une application console à l’intérieur du répertoire tests/RazorPagesProject.Tests
.
Répertoire de l’application de test | Description |
---|---|
AuthTests |
Contient des méthodes de test pour :
|
BasicTests |
Contient une méthode de test pour le routage et le type de contenu. |
IntegrationTests |
Contient les tests d’intégration pour la page Index à l’aide de la classe personnalisée WebApplicationFactory . |
Helpers/Utilities |
|
Le framework de test est xUnit. Les tests d’intégration sont effectués à l’aide de Microsoft.AspNetCore.TestHost, qui inclut le TestServer. Étant donné que le package Microsoft.AspNetCore.Mvc.Testing
est utilisé pour configurer l’hôte de test et le serveur de test, les packages TestHost
et TestServer
ne nécessitent pas de références de package directes dans le fichier projet de l’application de test ou la configuration du développeur dans l’application de test.
Les tests d’intégration nécessitent généralement un petit jeu de données dans la base de données avant l’exécution du test. Par exemple, un test de suppression appelle une suppression d’enregistrement de base de données, de sorte que la base de données doit avoir au moins un enregistrement pour que la requête de suppression aboutisse.
L’échantillon d’application amorcera la base de données avec trois messages dans Utilities.cs
que les tests peuvent utiliser lorsqu’ils s’exécutent :
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." }
};
}
Le contexte de base de données du ST est inscrit dans Program.cs
. Le rappel de l’application de test builder.ConfigureServices
est exécuté après l’exécution du code de l’application Program.cs
. Pour utiliser une autre base de données pour les tests, le contexte de base de données de l’application doit être remplacé dans builder.ConfigureServices
. Pour plus d’informations, consultez la section Personnaliser WebApplicationFactory .