ASP.NET Core : apprendre l'exécution de tests unitaires dans une application MVC avec MsTest V2 et des objets Mock,
Un tutoriel de Hinault Romaric
Le 05/09/2016, par Hinault Romaric, Responsable .NET
Dans mon précèdent billet de blog, j’ai présenté MsTest V2, la nouvelle version du framework de tests unitaires de Microsoft. Cette version est encore au stade de preview. Elle supporte le framework .NET Core. Dans ce billet, nous avons vu comment intégrer MsTest V2 à un projet et écrire des tests unitaires pour une application ASP.NET MVC Core.
Dans ce nouveau billet de blog, nous irons un peu plus loin et mous verrons comment écrire des tests unitaires mockés avec MsTest V2 et Moq.
Petit rappel sur le mocking
Lors du développement, il arrive fréquemment que dans une classe, nous fassions appel à plusieurs autres objets. Ce qui crée une dépendance entre les classes. Les tests unitaires ont pour objectifs de tester une unité de traitement (une méthode), sans avoir besoin de se soucier des dépendances avec d’autres classes (des objets qui sont appelés, et qui seront testés séparément).
Le but du mocking est de permettre aux développeurs de créer des objets simulés qui reproduisent le comportement désiré des objets réels, à leur invocation. Ces objets simulés sont couramment appelés Mock.
Il existe de nombreux frameworks .NET qui permettent de mettre en œuvre facilement le mocking. Ces frameworks permettent généralement de créer dynamiquement des objets à partir d’interfaces ou de classes. Ils offrent au développeur la possibilité de spécifier quelles méthodes vont être appelées et dans quel ordre elles le seront.
Dans le cadre de ce tutoriel, nous utiliserons le framework Moq, qui est une référence dans l’univers .NET. Ce dernier offre une prise ne charge de .NET core.
Ajout du package Moq au projet
Nous allons reprendre notre projet de tests du billet de blog précèdent. La première chose à faire sera d’installer le package Moq dans le projet de tests en utilisant la console NuGet. La commande à utiliser est la suivante :
Lorsque c’est fait, votre fichier project.json devrait ressembler à ceci :
Vous remarquez la présence de "Moq": "4.6.38-alpha".
Le contrôleur à tester
Nous allons tester un contrôleur qui dispose d’actions CRUD. Le code complet de ce contrôleur est le suivant :
Je ne vais pas écrire des tests pour avoir une couverture totale de ce code. Je vais me limiter au nécessaire permettant d’avoir divers scénarios.
Le code que nous devons tester utilise le pattern Repository et tire avantage des améliorations qui ont été apportées à ASP.NET Core pour offrir une meilleure prise en charge de l’injection des dépendances. Avec cette version, nous n’avons plus besoin, par exemple, de mettre en œuvre l’injection des dépendances au niveau du constructeur. Vous verrez combien cela va faciliter l’écriture de nos tests unitaires mockés.
Trêve de bavardage. Passons à la pratique.
Écriture des tests unitaires
Voici la première méthode pour laquelle nous allons écrire un test :
La méthode de test que nous allons écrire doit permettre de vérifier que le ViewResult contient la liste d'éléments qui a été retournée par le repository.
Nous allons premièrement créer un objet simulé de notre repository à partir de son interface :
Par la suite, nous allons changer le comportement de notre repository pour que lorsque la méthode GetAll() sera appelée dans notre méthode a tester, une autre méthode soit utilisée à la place :
La méthode qui est sera appelée à la place est GetTestStudents(), qui retourne une liste d’étudiants. Voici son code :
Ceci fait, nous allons passer l’instance de notre objet mocké au constructeur de StudentsController :
Par la suite, nous devons ajouter les assertions pour vérifier que le ViewResult retourne la liste d’éléments attendus :
Le code complet de notre méthode de test est le suivant :
Pour la suite, nous allons rédiger les tests pour la méthode d’action Details :
Pour ce cas, nous allons rédiger un test qui permet de vérifier que le ViewResult contient un objet étudiant, et deux autres pour vérifier qu’un NotFound result est retourné.
Pour le premier cas, la méthode _studentsRepository.Find(id.Value) est appelée dans notre action. Nous allons donc configurer notre objet mocké pour retourner un étudiant lorsque cette méthode est appelée avec une valeur précise en paramètre :
On va faire une assertion pour vérifier que l’information attendue est contenue dans le ViewResult :
Le code complet de la méthode de test est le suivant :
Pour le cas du NotFound result, nous avons deux cas de figure :
Pour le premier cas, nous allons configurer notre objet mocké pour qu’il retourne nul, lorsque la méthode Find() du repository est appelée avec la valeur “2” en paramètre :
Ensuite, on fait une assertion pour vérifier qu’un NotFoundResult est retourné :
Le code complet :
Pour le deuxième cas, nous n’aurons pas besoin de changer le comportement de notre objet mocké, car il ne sera pas appelé. Nous devons juste passer une valeur nulle a notre méthode d’action, ensuite vérifier qu’on obtient un NotFound result. Le code complet de cette méthode de test est le suivant :
Passons maintenant à la rédaction des tests unitaires pour la méthode d’action Create, dont voici le code :
Pour ce cas, nous allons rédiger deux tests :
Pour le premier cas, le code de la méthode de test permettant d’effectuer cela est le suivant :
Pour le second cas, nous devons modifier notre contrôleur pour que son model state soit invalide :
En effet, les tests unitaires se font sur une méthode isolée. L’appel de la méthode Create exécute uniquement cette dernière. De ce fait, il n’y a aucun passage au travers du pipeline ASP.NET MVC, qui devait s’occuper du binding du model et de la validation.
Le code complet pour notre méthode de test est le suivant :
Le code complet de notre classe de tests est le suivant :
A l’exécution, on obtient le résultat suivant :
Je crois qu’avec ces quelques exemples, j’ai couvert différents scénarios pour les tests unitaires mockés d’un contrôleur avec des actions CRUD. Vous devez être en mesure d’écrire sans beaucoup d’effort les tests pour couvrir les autres méthodes d’action.
Dans mon prochain billet, nous verrons comment rédiger des tests d’intégration en exploitant la fonctionnalité InMemory de Entity Framework Core. Restez connecter !
Dans ce nouveau billet de blog, nous irons un peu plus loin et mous verrons comment écrire des tests unitaires mockés avec MsTest V2 et Moq.
Petit rappel sur le mocking
Lors du développement, il arrive fréquemment que dans une classe, nous fassions appel à plusieurs autres objets. Ce qui crée une dépendance entre les classes. Les tests unitaires ont pour objectifs de tester une unité de traitement (une méthode), sans avoir besoin de se soucier des dépendances avec d’autres classes (des objets qui sont appelés, et qui seront testés séparément).
Le but du mocking est de permettre aux développeurs de créer des objets simulés qui reproduisent le comportement désiré des objets réels, à leur invocation. Ces objets simulés sont couramment appelés Mock.
Il existe de nombreux frameworks .NET qui permettent de mettre en œuvre facilement le mocking. Ces frameworks permettent généralement de créer dynamiquement des objets à partir d’interfaces ou de classes. Ils offrent au développeur la possibilité de spécifier quelles méthodes vont être appelées et dans quel ordre elles le seront.
Dans le cadre de ce tutoriel, nous utiliserons le framework Moq, qui est une référence dans l’univers .NET. Ce dernier offre une prise ne charge de .NET core.
Ajout du package Moq au projet
Nous allons reprendre notre projet de tests du billet de blog précèdent. La première chose à faire sera d’installer le package Moq dans le projet de tests en utilisant la console NuGet. La commande à utiliser est la suivante :
Code : |
Install-Package Moq -Pre
Code json : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | { "version": "1.0.0-*", "testRunner": "mstest", "dependencies": { "dotnet-test-mstest": "1.1.1-preview", "Moq": "4.6.38-alpha", "MSTest.TestAdapter": "1.0.3-preview", "MSTest.TestFramework": "1.0.1-preview", "NETStandard.Library": "1.6.0", "SampleApp": "1.0.0-*" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dnxcore50", "portable-net45+win8" ], "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" } } } } } |
Vous remarquez la présence de "Moq": "4.6.38-alpha".
Le contrôleur à tester
Nous allons tester un contrôleur qui dispose d’actions CRUD. Le code complet de ce contrôleur est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; using SampleApp.Models; using SampleApp.Repository; namespace SampleApp.Controllers { public class StudentsController : Controller { private readonly IStudentsRepository _studentsRepository; public StudentsController(IStudentsRepository studentsRepository) { _studentsRepository = studentsRepository; } // GET: Students public async Task<IActionResult> Index() { return View(await _studentsRepository.GetAll()); } // GET: Students/Details/5 public async Task<IActionResult> Details(int? id) { if (id == null) { return NotFound(); } var student = await _studentsRepository.Find(id.Value); if (student == null) { return NotFound(); } return View(student); } // GET: Students/Create public IActionResult Create() { return View(); } // POST: Students/Create // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Email,FirstName,LastName")] Student student) { if (ModelState.IsValid) { await _studentsRepository.Add(student); return RedirectToAction("Index"); } return View(student); } // GET: Students/Edit/5 public async Task<IActionResult> Edit(int? id) { if (id == null) { return NotFound(); } var student = await _studentsRepository.Find(id.Value); if (student == null) { return NotFound(); } return View(student); } // POST: Students/Edit/5 // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id, [Bind("Id,Email,FirstName,LastName")] Student student) { if (id != student.Id) { return NotFound(); } if (ModelState.IsValid) { try { await _studentsRepository.Update(student); } catch (DbUpdateConcurrencyException) { if (!await _studentsRepository.StudentExists(student.Id)) { return NotFound(); } else { throw; } } return RedirectToAction("Index"); } return View(student); } // GET: Students/Delete/5 public async Task<IActionResult> Delete(int? id) { if (id == null) { return NotFound(); } var student = await _studentsRepository.Find(id.Value); if (student == null) { return NotFound(); } return View(student); } // POST: Students/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<IActionResult> DeleteConfirmed(int id) { await _studentsRepository.Remove(id); return RedirectToAction("Index"); } } } |
Je ne vais pas écrire des tests pour avoir une couverture totale de ce code. Je vais me limiter au nécessaire permettant d’avoir divers scénarios.
Le code que nous devons tester utilise le pattern Repository et tire avantage des améliorations qui ont été apportées à ASP.NET Core pour offrir une meilleure prise en charge de l’injection des dépendances. Avec cette version, nous n’avons plus besoin, par exemple, de mettre en œuvre l’injection des dépendances au niveau du constructeur. Vous verrez combien cela va faciliter l’écriture de nos tests unitaires mockés.
Trêve de bavardage. Passons à la pratique.
Écriture des tests unitaires
Voici la première méthode pour laquelle nous allons écrire un test :
Code c# : |
1 2 3 4 5 | // GET: Students public async Task<IActionResult> Index() { return View(await _studentsRepository.GetAll()); } |
La méthode de test que nous allons écrire doit permettre de vérifier que le ViewResult contient la liste d'éléments qui a été retournée par le repository.
Nous allons premièrement créer un objet simulé de notre repository à partir de son interface :
Code c# : |
var studentsRepositoryMock = new Mock<IStudentsRepository>();
Par la suite, nous allons changer le comportement de notre repository pour que lorsque la méthode GetAll() sera appelée dans notre méthode a tester, une autre méthode soit utilisée à la place :
Code c# : |
studentsRepositoryMock.Setup(repo => repo.GetAll()).Returns(Task.FromResult(GetTestStudents()));
La méthode qui est sera appelée à la place est GetTestStudents(), qui retourne une liste d’étudiants. Voici son code :
Code c# : |
1 2 3 4 5 6 7 8 9 10 | private IEnumerable<Student> GetTestStudents() { IEnumerable<Student> students = new List<Student>() { new Student {Id = 1, Email = "j.papavoisi@gmail.com", FirstName="Papavoisi", LastName="Jean" }, new Student { Id = 2, Email = "p.garden@gmail.com", FirstName = "Garden", LastName = "Pierre" }, new Student { Id = 3, Email = "r.derosi@gmail.com", FirstName = "Derosi", LastName = "Ronald" } }; return students; } |
Ceci fait, nous allons passer l’instance de notre objet mocké au constructeur de StudentsController :
Code c# : |
var controller = new StudentsController(studentsRepositoryMock.Object);
Par la suite, nous devons ajouter les assertions pour vérifier que le ViewResult retourne la liste d’éléments attendus :
Code c# : |
1 2 3 | Assert.IsNotNull(viewResult); var students = viewResult.ViewData.Model as List<Student>; Assert.AreEqual(3, students.Count); |
Le code complet de notre méthode de test est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [TestMethod] public async Task Index_ReturnsAllStudents() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.GetAll()).Returns(Task.FromResult(GetTestStudents())); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var viewResult = await controller.Index() as ViewResult; //assert Assert.IsNotNull(viewResult); var students = viewResult.ViewData.Model as List<Student>; Assert.AreEqual(3, students.Count); } |
Pour la suite, nous allons rédiger les tests pour la méthode d’action Details :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public async Task<IActionResult> Details(int? id) { if (id == null) { return NotFound(); } var student = await _studentsRepository.Find(id.Value); if (student == null) { return NotFound(); } return View(student); } |
Pour ce cas, nous allons rédiger un test qui permet de vérifier que le ViewResult contient un objet étudiant, et deux autres pour vérifier qu’un NotFound result est retourné.
Pour le premier cas, la méthode _studentsRepository.Find(id.Value) est appelée dans notre action. Nous allons donc configurer notre objet mocké pour retourner un étudiant lorsque cette méthode est appelée avec une valeur précise en paramètre :
Code c# : |
studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult(GetTestStudents().ElementAt(1)));
On va faire une assertion pour vérifier que l’information attendue est contenue dans le ViewResult :
Code c# : |
1 2 3 | Assert.IsNotNull(viewResult); var student = viewResult.ViewData.Model as Student; Assert.AreEqual("Garden", student.FirstName); |
Le code complet de la méthode de test est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [TestMethod] public async Task Details_ReturnsStudent() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult(GetTestStudents().ElementAt(1))); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var viewResult = await controller.Details(2) as ViewResult; //assert Assert.IsNotNull(viewResult); var student = viewResult.ViewData.Model as Student; Assert.AreEqual("Garden", student.FirstName); } |
Pour le cas du NotFound result, nous avons deux cas de figure :
- -l’etudiant dont l’id a été spécifié n’a pas été trouvé ;
- -l’id passé est null.
Pour le premier cas, nous allons configurer notre objet mocké pour qu’il retourne nul, lorsque la méthode Find() du repository est appelée avec la valeur “2” en paramètre :
Code c# : |
studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult<Student>(null));
Ensuite, on fait une assertion pour vérifier qu’un NotFoundResult est retourné :
Code : |
=c#Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [TestMethod] public async Task Details_ReturnsNotFoundWithId() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult<Student>(null)); var controller = new StudentsController(studentsRepositoryMock.Object); // Act IActionResult actionResult = await controller.Details(2) ; //assert Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult)); } |
Pour le deuxième cas, nous n’aurons pas besoin de changer le comportement de notre objet mocké, car il ne sera pas appelé. Nous devons juste passer une valeur nulle a notre méthode d’action, ensuite vérifier qu’on obtient un NotFound result. Le code complet de cette méthode de test est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [TestMethod] public async Task Details_ReturnsNotFoundWithNullId() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act IActionResult actionResult = await controller.Details(null); //assert Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult)); } |
Passons maintenant à la rédaction des tests unitaires pour la méthode d’action Create, dont voici le code :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 | [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Email,FirstName,LastName")] Student student) { if (ModelState.IsValid) { await _studentsRepository.Add(student); return RedirectToAction("Index"); } return View(student); } |
Pour ce cas, nous allons rédiger deux tests :
- L’un qui permettra de vérifier la redirection ;
- L’autre pour le cas où le ModelState est invalide.
Pour le premier cas, le code de la méthode de test permettant d’effectuer cela est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [TestMethod] public async Task Create_ReturnsRedirectToAction() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var result = await controller.Create(new Student { Id=4, Email="a.Damien@gmail.com", FirstName="Damien", LastName="Alain" }) as RedirectToActionResult; //assert Assert.IsNotNull(result); Assert.AreEqual("Index", result.ActionName); } |
Pour le second cas, nous devons modifier notre contrôleur pour que son model state soit invalide :
Code : |
controller.ModelState.AddModelError("Email", "Required");
Le code complet pour notre méthode de test est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public async Task Create_InvalidModelState() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act controller.ModelState.AddModelError("Email", "Required"); var viewResult = await controller.Create(new Student ()) as ViewResult; //assert Assert.IsNotNull(viewResult); var student = viewResult.Model as Student; Assert.IsNotNull(student); } |
Le code complet de notre classe de tests est le suivant :
Code c# : |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | [TestClass] public class StudentsControllerTest { [TestMethod] public async Task Index_ReturnsAllStudents() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.GetAll()).Returns(Task.FromResult(GetTestStudents())); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var viewResult = await controller.Index() as ViewResult; //assert Assert.IsNotNull(viewResult); var students = viewResult.ViewData.Model as List<Student>; Assert.AreEqual(3, students.Count); } [TestMethod] public async Task Details_ReturnStudent() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult(GetTestStudents().ElementAt(1))); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var viewResult = await controller.Details(2) as ViewResult; //assert Assert.IsNotNull(viewResult); var student = viewResult.ViewData.Model as Student; Assert.AreEqual("Garden", student.FirstName); } [TestMethod] public async Task Details_ReturnsNotFoundWithId() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); studentsRepositoryMock.Setup(repo => repo.Find(2)).Returns(Task.FromResult<Student>(null)); var controller = new StudentsController(studentsRepositoryMock.Object); // Act IActionResult actionResult = await controller.Details(2) ; //assert Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult)); } [TestMethod] public async Task Details_ReturnsNotFoundWithNullId() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act IActionResult actionResult = await controller.Details(null); //assert Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult)); } [TestMethod] public async Task Create_ReturnsRedirectToAction() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act var result = await controller.Create(new Student { Id=4, Email="a.Damien@gmail.com", FirstName="Damien", LastName="Alain" }) as RedirectToActionResult; //assert Assert.IsNotNull(result); Assert.AreEqual("Index", result.ActionName); } [TestMethod] public async Task Create_InvalidModelState() { //Arrange var studentsRepositoryMock = new Mock<IStudentsRepository>(); var controller = new StudentsController(studentsRepositoryMock.Object); // Act controller.ModelState.AddModelError("Email", "Required"); var viewResult = await controller.Create(new Student ()) as ViewResult; //assert Assert.IsNotNull(viewResult); var student = viewResult.Model as Student; Assert.IsNotNull(student); } private IEnumerable<Student> GetTestStudents() { IEnumerable<Student> students = new List<Student>() { new Student {Id = 1, Email = "j.papavoisi@gmail.com", FirstName="Papavoisi", LastName="Jean" }, new Student { Id = 2, Email = "p.garden@gmail.com", FirstName = "Garden", LastName = "Pierre" }, new Student { Id = 3, Email = "r.derosi@gmail.com", FirstName = "Derosi", LastName = "Ronald" } }; return students; } } |
A l’exécution, on obtient le résultat suivant :
Je crois qu’avec ces quelques exemples, j’ai couvert différents scénarios pour les tests unitaires mockés d’un contrôleur avec des actions CRUD. Vous devez être en mesure d’écrire sans beaucoup d’effort les tests pour couvrir les autres méthodes d’action.
Dans mon prochain billet, nous verrons comment rédiger des tests d’intégration en exploitant la fonctionnalité InMemory de Entity Framework Core. Restez connecter !