Tests unitaires de pages Razor dans ASP.NET Core
ASP.NET Core prend en charge les tests unitaires d’applications Razor Pages. Les tests de la couche d’accès aux données (DAL) et des modèles de page permettent de s’assurer que :
- Les parties d’une application Razor Pages fonctionnent indépendamment et ensemble en tant qu’unité pendant la construction de l’application.
- Les classes et méthodes ont des étendues de responsabilité limitées.
- Il existe une documentation supplémentaire sur le comportement de l’application.
- Vous rencontrez les régressions, qui sont des erreurs provoquées par les mises à jour du code, lors de la génération et du déploiement automatisés.
Cette rubrique suppose que vous avez une compréhension de base des applications Razor Pages et des tests unitaires. Si vous n’êtes pas familiarisé avec les applications Razor Pages ou les concepts de test, consultez les rubriques suivantes :
- Introduction à Razor Pages dans ASP.NET Core
- Tutoriel : Bien démarrer avec Razor Pages dans ASP.NET Core
- Effectuer des tests unitaires de C# dans .NET Core à l’aide de dotnet test et de xUnit
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
L’exemple de projet se compose de deux applications :
Application | Dossier du projet | Description |
---|---|---|
Application de message | src/RazorPagesTestSample | Permet à un utilisateur d’ajouter un message, de supprimer un message, de supprimer tous les messages et d’analyser les messages (rechercher le nombre moyen de mots par message). |
Tester une application | tests/RazorPagesTestSample.Tests | Permet de tester unitairement la couche d’accès aux données et le modèle de page Index de l’application de message. |
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 dossier tests/RazorPagesTestSample.Tests :
dotnet test
Organisation de l’application de message
L’application de message est un système de messages Razor Pages 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 (recherche du nombre moyen de mots 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
). Les méthodes de la couche d’accès aux données sont marquéesvirtual
, ce qui permet de simuler les méthodes à utiliser dans les tests. - Si la base de données est vide au démarrage de l’application, la banque de messages est initialisée avec trois messages. Ces messages amorcés sont également utilisés dans les tests.
†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’exemple d’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 dans ASP.NET Core (l’exemple implémente le modèle de référentiel).
Tester l’organisation de l’application
L’application de test est une application console dans le dossier tests/RazorPagesTestSample.Tests.
Dossier d’application de test | Description |
---|---|
UnitTests |
|
Utilitaires | Contient la méthode TestDbContextOptions utilisée pour créer de nouvelles options de contexte de base de données pour chaque test unitaire de la couche d’accès aux données afin que la base de données soit réinitialisée à sa condition de base de référence pour chaque test. |
Le framework de test est xUnit. Le framework de simulation d’objet est Moq.
Tests unitaires de la couche d’accès aux données (DAL)
L’application de message a une couche d’accès aux données avec quatre méthodes contenues dans la classe AppDbContext
(src/RazorPagesTestSample/Data/AppDbContext.cs
). Chaque méthode a un ou deux tests unitaires dans l’application de test.
Méthode de couche d’accès aux données | Fonction |
---|---|
GetMessagesAsync |
Obtient un List<Message> à partir de la base de données triée sur la propriété Text . |
AddMessageAsync |
Ajoute un Message à la base de données. |
DeleteAllMessagesAsync |
Supprime toutes les entrées Message de la base de données. |
DeleteMessageAsync |
Supprime un seul Message de la base de données par Id . |
Les tests unitaires de la couche d’accès aux données nécessitent DbContextOptions lors de la création d’un nouveau AppDbContext
pour chaque test. Une approche de la création de DbContextOptions
pour chaque test consiste à utiliser un DbContextOptionsBuilder :
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Le problème avec cette approche est que chaque test reçoit la base de données dans l’état laissé par le test précédent. Cela peut être problématique lorsque vous essayez d’écrire des tests unitaires atomiques qui n’interfèrent pas les uns avec les autres. Pour forcer le AppDbContext
à utiliser un nouveau contexte de base de données pour chaque test, fournissez une instance DbContextOptions
basée sur un nouveau fournisseur de services. L’application de test montre comment procéder à l’aide de la méthode TestDbContextOptions
de sa classe Utilities
(tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs
) :
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
L’utilisation du DbContextOptions
dans les tests unitaires de couche d’accès aux données permet à chaque test de s’exécuter atomiquement avec une nouvelle instance de base de données :
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Chaque méthode de test de la classe DataAccessLayerTest
(UnitTests/DataAccessLayerTest.cs
) suit un modèle Organisation, Action, Assertion similaire :
- Organisation : la base de données est configurée pour le test et/ou le résultat attendu est défini.
- Action : le test est exécuté.
- Assertion : des assertions sont effectuées pour déterminer si le résultat du test est un succès.
Par exemple, la méthode DeleteMessageAsync
est chargée de supprimer un message unique identifié par son Id
(src/RazorPagesTestSample/Data/AppDbContext.cs
) :
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Il existe deux tests pour cette méthode. Un test vérifie que la méthode supprime un message lorsque le message est présent dans la base de données. L’autre méthode teste que la base de données ne change pas si le message à supprimer (Id
) n’existe pas. Voici la méthode DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Tout d’abord, la méthode effectue l’étape Organisation, où la préparation de l’étape Action a lieu. Les messages d’amorçage sont obtenus et conservés dans seedMessages
. Les messages d’amorçage sont enregistrés dans la base de données. Le message avec un Id
de 1
est défini pour suppression. Lorsque la méthode DeleteMessageAsync
est exécutée, les messages attendus doivent comprendre tous les messages à l’exception de celui avec un Id
de 1
. La variable expectedMessages
représente ce résultat attendu.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
La méthode agit : la méthode DeleteMessageAsync
est exécutée en passant le recId
de 1
:
// Act
await db.DeleteMessageAsync(recId);
Enfin, la méthode obtient Messages
à partir du contexte et le compare à expectedMessages
pour affirmer que les deux sont égaux :
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Afin de comparer que les deux List<Message>
pour déterminer s’ils sont égaux :
- Les messages sont classés par
Id
. - Les paires de messages sont comparées sur la propriété
Text
.
Une méthode de test similaire, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
, vérifie le résultat de la tentative de suppression d’un message qui n’existe pas. Dans ce cas, les messages attendus dans la base de données doivent être égaux aux messages réels après l’exécution de la méthode DeleteMessageAsync
. Le contenu de la base de données ne doit pas être modifié :
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
try
{
await db.DeleteMessageAsync(recId);
}
catch
{
// recId doesn't exist
}
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Tests unitaires des méthodes de modèle de page
Un autre ensemble de tests unitaires est responsable des tests des méthodes de modèle de page. Dans l’application de message, les modèles de page Index se trouvent dans la classe IndexModel
dans src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Méthode de modèle de page | Fonction |
---|---|
OnGetAsync |
Obtient les messages de la couche d’accès aux données pour l’interface utilisateur à l’aide de la méthode GetMessagesAsync . |
OnPostAddMessageAsync |
Si ModelState est valide, appelle AddMessageAsync pour ajouter un message à la base de données. |
OnPostDeleteAllMessagesAsync |
Appelle DeleteAllMessagesAsync pour supprimer tous les messages de la base de données. |
OnPostDeleteMessageAsync |
Exécute DeleteMessageAsync pour supprimer un message avec la valeur Id spécifiée. |
OnPostAnalyzeMessagesAsync |
Si un ou plusieurs messages se trouvent dans la base de données, calcule le nombre moyen de mots par message. |
Les méthodes de modèle de page sont testées à l’aide de sept tests dans la classe IndexPageTests
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). Les tests utilisent le modèle Organisation, Action, Assertion familier. Ces tests visent à :
- Déterminer si les méthodes suivent le comportement correct lorsque ModelState n’est pas valide.
- Confirmer que les méthodes produisent le bon IActionResult.
- Vérifier que les affectations de valeurs de propriété sont effectuées correctement.
Ce groupe de tests simule souvent des méthodes de la couche d’accès aux données pour produire les données attendues pour l’étape Action où une méthode de modèle de page est exécutée. Par exemple, la méthode GetMessagesAsync
de AppDbContext
est simulée pour produire une sortie. Lorsqu’une méthode de modèle de page exécute cette méthode, la simulation retourne le résultat. Les données ne proviennent pas de la base de données. Cela crée des conditions de test prévisibles et fiables pour l’utilisation de la couche d’accès aux données dans les tests de modèle de page.
Le test OnGetAsync_PopulatesThePageModel_WithAListOfMessages
montre comment la méthode GetMessagesAsync
est simulée pour le modèle de page :
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
Lorsque la méthode OnGetAsync
est exécutée à l’étape Action, elle appelle la méthode GetMessagesAsync
du modèle de page.
Étape Action du test unitaire (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
) :
// Act
await pageModel.OnGetAsync();
Méthode OnGetAsync
du modèle de page IndexPage
(src/RazorPagesTestSample/Pages/Index.cshtml.cs
) :
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
La méthode GetMessagesAsync
dans la couche d’accès aux données ne retourne pas le résultat de cet appel de méthode. La version simulée de la méthode retourne le résultat.
Dans l’étape Assert
, les messages réels (actualMessages
) sont attribués à partir de la propriété Messages
du modèle de page. Une vérification de type est également effectuée lorsque les messages sont affectés. Les messages attendus et réels sont comparés sur leurs propriétés Text
. Le test affirme que les deux instances List<Message>
contiennent les mêmes messages.
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
D’autres tests de ce groupe créent des objets de modèle de page qui incluent DefaultHttpContext, ModelStateDictionary, un ActionContext pour établir PageContext
, un ViewDataDictionary
et un PageContext
. Ceux-ci sont utiles pour effectuer les tests. Par exemple, l’application de message établit une erreur ModelState
avec AddModelError pour vérifier qu’un PageResult valide est retourné lorsque OnPostAddMessageAsync
est exécuté :
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
Ressources supplémentaires
- Effectuer des tests unitaires de C# dans .NET Core à l’aide de dotnet test et de xUnit
- Tester la logique des contrôleurs dans ASP.NET Core
- Test unitaire de votre code (Visual Studio)
- Tests d’intégration dans ASP.NET Core
- xUnit.net
- Prise en main de xUnit.net : utilisation de .NET Core avec la ligne de commande du SDK .NET
- Moq
- Démarrage rapide de Moq
ASP.NET Core prend en charge les tests unitaires d’applications Razor Pages. Les tests de la couche d’accès aux données (DAL) et des modèles de page permettent de s’assurer que :
- Les parties d’une application Razor Pages fonctionnent indépendamment et ensemble en tant qu’unité pendant la construction de l’application.
- Les classes et méthodes ont des étendues de responsabilité limitées.
- Il existe une documentation supplémentaire sur le comportement de l’application.
- Vous rencontrez les régressions, qui sont des erreurs provoquées par les mises à jour du code, lors de la génération et du déploiement automatisés.
Cette rubrique suppose que vous avez une compréhension de base des applications Razor Pages et des tests unitaires. Si vous n’êtes pas familiarisé avec les applications Razor Pages ou les concepts de test, consultez les rubriques suivantes :
- Introduction à Razor Pages dans ASP.NET Core
- Tutoriel : Bien démarrer avec Razor Pages dans ASP.NET Core
- Effectuer des tests unitaires de C# dans .NET Core à l’aide de dotnet test et de xUnit
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
L’exemple de projet se compose de deux applications :
Application | Dossier du projet | Description |
---|---|---|
Application de message | src/RazorPagesTestSample | Permet à un utilisateur d’ajouter un message, de supprimer un message, de supprimer tous les messages et d’analyser les messages (rechercher le nombre moyen de mots par message). |
Tester une application | tests/RazorPagesTestSample.Tests | Permet de tester unitairement la couche d’accès aux données et le modèle de page Index de l’application de message. |
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 dossier tests/RazorPagesTestSample.Tests :
dotnet test
Organisation de l’application de message
L’application de message est un système de messages Razor Pages 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 (recherche du nombre moyen de mots 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
). Les méthodes de la couche d’accès aux données sont marquéesvirtual
, ce qui permet de simuler les méthodes à utiliser dans les tests. - Si la base de données est vide au démarrage de l’application, la banque de messages est initialisée avec trois messages. Ces messages amorcés sont également utilisés dans les tests.
†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’exemple d’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 dans ASP.NET Core (l’exemple implémente le modèle de référentiel).
Tester l’organisation de l’application
L’application de test est une application console dans le dossier tests/RazorPagesTestSample.Tests.
Dossier d’application de test | Description |
---|---|
UnitTests |
|
Utilitaires | Contient la méthode TestDbContextOptions utilisée pour créer de nouvelles options de contexte de base de données pour chaque test unitaire de la couche d’accès aux données afin que la base de données soit réinitialisée à sa condition de base de référence pour chaque test. |
Le framework de test est xUnit. Le framework de simulation d’objet est Moq.
Tests unitaires de la couche d’accès aux données (DAL)
L’application de message a une couche d’accès aux données avec quatre méthodes contenues dans la classe AppDbContext
(src/RazorPagesTestSample/Data/AppDbContext.cs
). Chaque méthode a un ou deux tests unitaires dans l’application de test.
Méthode de couche d’accès aux données | Fonction |
---|---|
GetMessagesAsync |
Obtient un List<Message> à partir de la base de données triée sur la propriété Text . |
AddMessageAsync |
Ajoute un Message à la base de données. |
DeleteAllMessagesAsync |
Supprime toutes les entrées Message de la base de données. |
DeleteMessageAsync |
Supprime un seul Message de la base de données par Id . |
Les tests unitaires de la couche d’accès aux données nécessitent DbContextOptions lors de la création d’un nouveau AppDbContext
pour chaque test. Une approche de la création de DbContextOptions
pour chaque test consiste à utiliser un DbContextOptionsBuilder :
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
using (var db = new AppDbContext(optionsBuilder.Options))
{
// Use the db here in the unit test.
}
Le problème avec cette approche est que chaque test reçoit la base de données dans l’état laissé par le test précédent. Cela peut être problématique lorsque vous essayez d’écrire des tests unitaires atomiques qui n’interfèrent pas les uns avec les autres. Pour forcer le AppDbContext
à utiliser un nouveau contexte de base de données pour chaque test, fournissez une instance DbContextOptions
basée sur un nouveau fournisseur de services. L’application de test montre comment procéder à l’aide de la méthode TestDbContextOptions
de sa classe Utilities
(tests/RazorPagesTestSample.Tests/Utilities/Utilities.cs
) :
public static DbContextOptions<AppDbContext> TestDbContextOptions()
{
// Create a new service provider to create a new in-memory database.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Create a new options instance using an in-memory database and
// IServiceProvider that the context should resolve all of its
// services from.
var builder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb")
.UseInternalServiceProvider(serviceProvider);
return builder.Options;
}
L’utilisation du DbContextOptions
dans les tests unitaires de couche d’accès aux données permet à chaque test de s’exécuter atomiquement avec une nouvelle instance de base de données :
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Use the db here in the unit test.
}
Chaque méthode de test de la classe DataAccessLayerTest
(UnitTests/DataAccessLayerTest.cs
) suit un modèle Organisation, Action, Assertion similaire :
- Organisation : la base de données est configurée pour le test et/ou le résultat attendu est défini.
- Action : le test est exécuté.
- Assertion : des assertions sont effectuées pour déterminer si le résultat du test est un succès.
Par exemple, la méthode DeleteMessageAsync
est chargée de supprimer un message unique identifié par son Id
(src/RazorPagesTestSample/Data/AppDbContext.cs
) :
public async virtual Task DeleteMessageAsync(int id)
{
var message = await Messages.FindAsync(id);
if (message != null)
{
Messages.Remove(message);
await SaveChangesAsync();
}
}
Il existe deux tests pour cette méthode. Un test vérifie que la méthode supprime un message lorsque le message est présent dans la base de données. L’autre méthode teste que la base de données ne change pas si le message à supprimer (Id
) n’existe pas. Voici la méthode DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound
:
[Fact]
public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Tout d’abord, la méthode effectue l’étape Organisation, où la préparation de l’étape Action a lieu. Les messages d’amorçage sont obtenus et conservés dans seedMessages
. Les messages d’amorçage sont enregistrés dans la base de données. Le message avec un Id
de 1
est défini pour suppression. Lorsque la méthode DeleteMessageAsync
est exécutée, les messages attendus doivent comprendre tous les messages à l’exception de celui avec un Id
de 1
. La variable expectedMessages
représente ce résultat attendu.
// Arrange
var seedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(seedMessages);
await db.SaveChangesAsync();
var recId = 1;
var expectedMessages =
seedMessages.Where(message => message.Id != recId).ToList();
La méthode agit : la méthode DeleteMessageAsync
est exécutée en passant le recId
de 1
:
// Act
await db.DeleteMessageAsync(recId);
Enfin, la méthode obtient Messages
à partir du contexte et le compare à expectedMessages
pour affirmer que les deux sont égaux :
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
Afin de comparer que les deux List<Message>
pour déterminer s’ils sont égaux :
- Les messages sont classés par
Id
. - Les paires de messages sont comparées sur la propriété
Text
.
Une méthode de test similaire, DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound
, vérifie le résultat de la tentative de suppression d’un message qui n’existe pas. Dans ce cas, les messages attendus dans la base de données doivent être égaux aux messages réels après l’exécution de la méthode DeleteMessageAsync
. Le contenu de la base de données ne doit pas être modifié :
[Fact]
public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
{
using (var db = new AppDbContext(Utilities.TestDbContextOptions()))
{
// Arrange
var expectedMessages = AppDbContext.GetSeedingMessages();
await db.AddRangeAsync(expectedMessages);
await db.SaveChangesAsync();
var recId = 4;
// Act
await db.DeleteMessageAsync(recId);
// Assert
var actualMessages = await db.Messages.AsNoTracking().ToListAsync();
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
}
}
Tests unitaires des méthodes de modèle de page
Un autre ensemble de tests unitaires est responsable des tests des méthodes de modèle de page. Dans l’application de message, les modèles de page Index se trouvent dans la classe IndexModel
dans src/RazorPagesTestSample/Pages/Index.cshtml.cs
.
Méthode de modèle de page | Fonction |
---|---|
OnGetAsync |
Obtient les messages de la couche d’accès aux données pour l’interface utilisateur à l’aide de la méthode GetMessagesAsync . |
OnPostAddMessageAsync |
Si ModelState est valide, appelle AddMessageAsync pour ajouter un message à la base de données. |
OnPostDeleteAllMessagesAsync |
Appelle DeleteAllMessagesAsync pour supprimer tous les messages de la base de données. |
OnPostDeleteMessageAsync |
Exécute DeleteMessageAsync pour supprimer un message avec la valeur Id spécifiée. |
OnPostAnalyzeMessagesAsync |
Si un ou plusieurs messages se trouvent dans la base de données, calcule le nombre moyen de mots par message. |
Les méthodes de modèle de page sont testées à l’aide de sept tests dans la classe IndexPageTests
(tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
). Les tests utilisent le modèle Organisation, Action, Assertion familier. Ces tests visent à :
- Déterminer si les méthodes suivent le comportement correct lorsque ModelState n’est pas valide.
- Confirmer que les méthodes produisent le bon IActionResult.
- Vérifier que les affectations de valeurs de propriété sont effectuées correctement.
Ce groupe de tests simule souvent des méthodes de la couche d’accès aux données pour produire les données attendues pour l’étape Action où une méthode de modèle de page est exécutée. Par exemple, la méthode GetMessagesAsync
de AppDbContext
est simulée pour produire une sortie. Lorsqu’une méthode de modèle de page exécute cette méthode, la simulation retourne le résultat. Les données ne proviennent pas de la base de données. Cela crée des conditions de test prévisibles et fiables pour l’utilisation de la couche d’accès aux données dans les tests de modèle de page.
Le test OnGetAsync_PopulatesThePageModel_WithAListOfMessages
montre comment la méthode GetMessagesAsync
est simulée pour le modèle de page :
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(
db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var pageModel = new IndexModel(mockAppDbContext.Object);
Lorsque la méthode OnGetAsync
est exécutée à l’étape Action, elle appelle la méthode GetMessagesAsync
du modèle de page.
Étape Action du test unitaire (tests/RazorPagesTestSample.Tests/UnitTests/IndexPageTests.cs
) :
// Act
await pageModel.OnGetAsync();
Méthode OnGetAsync
du modèle de page IndexPage
(src/RazorPagesTestSample/Pages/Index.cshtml.cs
) :
public async Task OnGetAsync()
{
Messages = await _db.GetMessagesAsync();
}
La méthode GetMessagesAsync
dans la couche d’accès aux données ne retourne pas le résultat de cet appel de méthode. La version simulée de la méthode retourne le résultat.
Dans l’étape Assert
, les messages réels (actualMessages
) sont attribués à partir de la propriété Messages
du modèle de page. Une vérification de type est également effectuée lorsque les messages sont affectés. Les messages attendus et réels sont comparés sur leurs propriétés Text
. Le test affirme que les deux instances List<Message>
contiennent les mêmes messages.
// Assert
var actualMessages = Assert.IsAssignableFrom<List<Message>>(pageModel.Messages);
Assert.Equal(
expectedMessages.OrderBy(m => m.Id).Select(m => m.Text),
actualMessages.OrderBy(m => m.Id).Select(m => m.Text));
D’autres tests de ce groupe créent des objets de modèle de page qui incluent DefaultHttpContext, ModelStateDictionary, un ActionContext pour établir PageContext
, un ViewDataDictionary
et un PageContext
. Ceux-ci sont utiles pour effectuer les tests. Par exemple, l’application de message établit une erreur ModelState
avec AddModelError pour vérifier qu’un PageResult valide est retourné lorsque OnPostAddMessageAsync
est exécuté :
[Fact]
public async Task OnPostAddMessageAsync_ReturnsAPageResult_WhenModelStateIsInvalid()
{
// Arrange
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("InMemoryDb");
var mockAppDbContext = new Mock<AppDbContext>(optionsBuilder.Options);
var expectedMessages = AppDbContext.GetSeedingMessages();
mockAppDbContext.Setup(db => db.GetMessagesAsync()).Returns(Task.FromResult(expectedMessages));
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = new TempDataDictionary(httpContext, Mock.Of<ITempDataProvider>());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData
};
var pageModel = new IndexModel(mockAppDbContext.Object)
{
PageContext = pageContext,
TempData = tempData,
Url = new UrlHelper(actionContext)
};
pageModel.ModelState.AddModelError("Message.Text", "The Text field is required.");
// Act
var result = await pageModel.OnPostAddMessageAsync();
// Assert
Assert.IsType<PageResult>(result);
}
Ressources supplémentaires
- Effectuer des tests unitaires de C# dans .NET Core à l’aide de dotnet test et de xUnit
- Tester la logique des contrôleurs dans ASP.NET Core
- Test unitaire de votre code (Visual Studio)
- Tests d’intégration dans ASP.NET Core
- xUnit.net
- Prise en main de xUnit.net : utilisation de .NET Core avec la ligne de commande du SDK .NET
- Moq
- Démarrage rapide de Moq
- JustMockLite : framework de simulation pour les développeurs .NET. (Non géré ou pris en charge par Microsoft.)