[Actualité] Entity Framework Core : tests d’integration avec InMemory
par
, 04/09/2016 à 21h40 (2857 Affichages)
Ce nouveau billet de blog s’inscrit dans la même thématique que le précédent : les tests.
Dans mon précédent billet, j’ai présenté comment vous pouvez écrire des tests unitaires mockés pour une application ASP.NET Core en utilisant MsTest V2 et Moq.
Les objets mockés permettent aux développeurs de créer des objets simulés qui reproduisent le comportement désiré des objets réels, à leur invocation. Avec les mocks, le développeur peut tester une unité de traitement (une méthode), sans avoir besoin de se soucier des dépendances avec d’autres classes. En isolant la méthode à tester, il est rassuré que si le test échoue, la cause réside dans le code et pas ailleurs.
Toutefois, dans le cycle de développement, le développeur va arriver à une phase où il aura besoin de tester au complet une fonctionnalité, qui fait intervenir plusieurs unités de traitement. A ce stade, on parle couramment de test d’intégration.
Prenons l’exemple d’une application ASP.NET MVC qui utilise Entity Framework et une base de données SQL Server. Pour effectuer des tests d’intégration, sans avoir à impacter la base de données existante, le développeur va mettre des efforts sur la duplication de son « contexte » qui sera utilisé pour les tests.
Entity Framework Core apporte le concept de base de données en mémoire (InMemory). Le provider InMemory permet de tester des composants en simulant un accès à la base de données comme dans un contexte d’utilisation réelle, sans toutefois impacter la base de données existante. De plus, cette option réduit les efforts pour mettre en œuvre le mocking.
Trêve de bavardage. Passons à la pratique.
Nous allons reprendre notre application d’exemple du précédent billet. La première chose à faire sera d’ajouter une référence au package “Microsoft.EntityFrameworkCore.InMemory” dans le projet de test. Tapez la commande suivante dans la console NuGet.
Pour commencer, nous devons créer une méthode qui va permettre de définir les options du DbContext (DbContextOptions).
Code : Sélectionner tout - Visualiser dans une fenêtre à part Install-Package Microsoft.EntityFrameworkCore.InMemory
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 private static DbContextOptions<SampleAppContext> CreateNewContextOptions() { }
Dans cette méthode, nous allons dans un premier temps créer un nouveau ServiceProvider, qui va entrainer la génération d’une nouvelle instance d’une base de données InMemory.
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider();
Ensuite, nous allons créer une nouvelle instance du DbContextOptions, qui va permettre de spécifier à notre DbContext que nous souhaitons utiliser une base de données InMemory et notre nouveau serviceProvider. Le code pour effectuer cela est le suivant :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 var builder = new DbContextOptionsBuilder<SampleAppContext>(); builder.UseInMemoryDatabase() .UseInternalServiceProvider(serviceProvider);
Pour finir, nous allons retourner nos nouvelles options pour notre DbContext :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2return builder.Options;
Le code complet de cette méthode est le suivant :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 private static DbContextOptions<SampleAppContext> CreateNewContextOptions() { var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); var builder = new DbContextOptionsBuilder<SampleAppContext>(); builder.UseInMemoryDatabase() .UseInternalServiceProvider(serviceProvider); return builder.Options; }
Dans notre stratégie de test, nous souhaitons que chaque méthode de test s’exécute avec une base de données InMemory contenant un certain nombre d’informations. Pour cela, nous devons ajouter à notre test une méthode d’initialisation ayant l’attribut [TestInitialize]
:
Dans cette méthode, nous allons écrire le code permettant d’initialiser notre base de données InMemory.
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 [TestInitialize] public async Task Init() { }
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 [TestInitialize] public async Task Init() { var options = CreateNewContextOptions(); _studentRepository = new StudentsRepository(new SampleAppContext(options)); _studentRepository.Add(new Student { Id = 1, Email = "j.papavoisi@gmail.com", FirstName = "Papavoisi", LastName = "Jean" }); _studentRepository.Add(new Student { Id = 2, Email = "p.garden@gmail.com", FirstName = "Garden", LastName = "Pierre" }); _studentRepository.Add(new Student { Id = 3, Email = "r.derosi@gmail.com", FirstName = "Derosi", LastName = "Ronald" }); await _studentRepository.Save(); }
Passons maintenant à l’écriture de nos méthodes de test. Nous allons reprendre le contrôleur de notre exemple précédent. Je prends pour prérequis le fait que vous avez lu mon billet de blog précédent. Donc, je vais m’abstenir d’entrer dans les détails, et j’écrirais juste quelques méthodes de test.
Commençons par la méthode d’action Index(), qui retourne la liste des étudiants :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 public async Task<IActionResult> Index() { return View(await _studentsRepository.GetAll()); }
Le code de test pour cette dernière est le suivant :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 [TestMethod] public async Task Index_ReturnsAllStudentsIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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); }
Passons à la méthode d’action Details :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
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); }
Le code pour tester ce dernier avec un id qui existe dans la base de données est le suivant :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 [TestMethod] public async Task Details_ReturnStudentIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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); }
Enfin, nous allons écrire le code pour tester la méthode d’action Create() :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 public async Task<IActionResult> Create([Bind("Id,Email,FirstName,LastName")] Student student) { if (ModelState.IsValid) { _studentsRepository.Add(student); await _studentsRepository.Save(); return RedirectToAction("Index"); } return View(student); }
Pour ce dernier cas, voici le code de la méthode de test correspondante :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 [TestMethod] public async Task Create_ReturnsRedirectToActionIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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 finir, ci-dessous le code complet de notre classe de tests :
Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
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 [TestClass] public class StudentsControllerTestIN { private IStudentsRepository _studentRepository; private static DbContextOptions<SampleAppContext> CreateNewContextOptions() { var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); var builder = new DbContextOptionsBuilder<SampleAppContext>(); builder.UseInMemoryDatabase() .UseInternalServiceProvider(serviceProvider); return builder.Options; } [TestInitialize] public async Task Init() { var options = CreateNewContextOptions(); _studentRepository = new StudentsRepository(new SampleAppContext(options)); _studentRepository.Add(new Student { Id = 1, Email = "j.papavoisi@gmail.com", FirstName = "Papavoisi", LastName = "Jean" }); _studentRepository.Add(new Student { Id = 2, Email = "p.garden@gmail.com", FirstName = "Garden", LastName = "Pierre" }); _studentRepository.Add(new Student { Id = 3, Email = "r.derosi@gmail.com", FirstName = "Derosi", LastName = "Ronald" }); await _studentRepository.Save(); } [TestMethod] public async Task Index_ReturnsAllStudentsIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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_ReturnStudentIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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 Create_ReturnsRedirectToActionIn() { //Arrange var controller = new StudentsController(_studentRepository); // 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); } }
Au travers de ce billet de blog, vous avez découvert comment utiliser la fonctionnalité InMemory pour effectuer des tests en simulant un accès à votre base de données comme dans un contexte d’utilisation réelle, sans toutefois impacter votre base de données. De plus, l’utilisation de InMemory est pratique dans les situations où il faut de gros efforts pour mettre en œuvre le mocking.