Tester un contrôleur avec les mocks
Tests unitaires
- On a déjà vu au Sprint 2 comment tester nos Services
- Maintenant, on apprendre à tester nos contrôleurs
- Il sera particulièrement important de tester l’API,
- L’API aura beaucoup plus de trafic que l’app MVC
- Il est plus probable qu’un méchant « hacker » armé d’outils tel que Postman tente de brisé l’API
Tester un ActionResult
Se fier au projet exemple disponible sur GitHub Projet GitHub de tests de résultat de contrôleur
Moq se retrouve dans NuGet
Moq - Création
- Créer un mock
Mock<UsersService> usersServiceMock = new Mock<UsersService>();
- Nous aurons donc un faux service que l’on pourra configurer
- L’option Callbase permet de mocker seulement une partie une partie de l’objet et d’utiliser le code original dans les autres cas
Mock<TripsController> tripsControllerMock = new Mock<TripsController>() { CallBase = true };
- Nous pourrons ainsi utiliser des mocks avec les services du contrôleur, mais appeler les vrai actions de l’objet
On ÉVITE GÉNÉRALEMENT de mocker l'objet que l'on test! On veut mocker ses dépendances! Mais le contrôleur est un cas spécial car il contient un User qui est difficile à configurer!
Moq - Configuration
- Pour que moq puisse « mocker » (simuler) une méthode ou une propriété, celle-ci doit absolument être virtual
- Moq fait des override sur nos méthodes et propriétés
- Pour configurer une méthode ou une propriété, on utilise .Setup
- Méthodes
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
- Propriétés
mock.Setup(foo => foo.Name).Returns("bar");
Il est possible de configurer les paramètres que notre méthode reçoit
- It.IsAny
- Exécute le return pour toutes les valeurs possibles
mock.Setup(foo => foo.DoSomething(It.IsAny<string>())).Returns(true);
- It.Is
- Permet de configurer une certaine plage de données
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true);
Pour une fonction qui retourne null
Pour retrourner null, on peut généralement simplement RIEN FAIRE, car le comportement par défaut d'un Mock est de retourné default, donc null pour un objet. Mais si vous voulez le faire pour être clair, pour une utilisation de CallBase=true ou encore dans une séquence, il est nécessaire de faire un cast.
tripsServiceMock.Setup(s => s.Get(It.IsAny<int>())).Returns((Trip?)null);
Ou encore
tripsServiceMock.Setup(s => s.Get(It.IsAny<int>())).Returns(value: null);
Pour forcer le Mock à lancer une exception
Si l'on veut lancer une exception avec notre Mock, on peut simplement utiliser Throws à la place de Returns
Dans ce cas, un appel à DoSomething du service mocké va toujours lancer une exception de type MyException
serviceMock.Setup(s => s.DoSomething(It.IsAny<string>())).Throws(new MyException());
Dans ce cas, c'est seulement lorsque l'on appel la méthode DoSomething avec 42 que l'exception se produit et elle contient un message.
serviceMock.Setup(s => s.DoSomething(42)).Throws(new MyException("Mon Message"));
Pour une fonction qui retourne void
Comme que le comportement par défaut est de rien faire lorsqu’un méthode de notre mock n’est pas configure, il n’est pas souvent nécessairement de configurer une méthode qui retourne void toutefois, c’est possible en utilisant .Verifiable
tripsServiceMock.Setup(s => s.Delete(It.IsAny<int>())).Verifiable();
La méthode Callback
Permet d’exécuter du code lorsque la méthode est appelée Exemple, simuler une suppression Le callback enlève un élément de la liste
tripsServiceMock.Setup(s => s.Delete(It.IsAny<int>())).Callback((int id) =>
{
allTrips.Remove(trip);
}).Verifiable();
Utiliser le mock
La propriété .Object contient l’objet de référence qu’on peut utiliser comme l’objet normal
tripsServiceMock.Object.Delete(1);
Appel la configuration qu’on a fait
tripsControllerMock.Object.Delete()
Appel la vrai action Delete de notre contrôleur SI on avait utilisé l’option Callbase lors de la création
Comment faire si une action contient le code suivant?
User.FindFirstValue(ClaimTypes.NameIdentifier)
Étape 1
Faire une propriété pouvant être mockée dans notre contrôleur
Public virtual string UserId { get { return User.FindFirstValue(ClaimTypes.NameIdentifier)!; } }
Étape 2
Faire un mock dans nos tests du contrôleur utilisant le vrai code
Mock<TripsController> tripsControllerMock = new Mock<TripsController>() { CallBase = true };
Étape 3
Faire la configuration de la propriété UserId
tripsControllerMock.Setup(t => t.UserId).Returns("2");
Étape 4
Utiliser notre mock (Dans cet exemple, le contrôleur a retourné un Ok sans paramètre et le type de retour du résultat de l'action est OkResult)
var actionresult = tripsControllerMock.Object.Delete(1)
var result = actionresult.Result as OkResult;
Assert.IsNotNull(result);
ActionResult
Lorsque l'on appel une action, le type retourné est un ActionResult<T> et l'on construit l'objet retourné en appelant une méthode comme return Ok(value) ou return BadRequest()
Si on porte attention, on voit qu'il n'y a PAS de new, on ne retourne pas un objet Ok ou BadRequest, mais on appel la méthode Ok ou BadRequest qui va créer un objet.
Si on regarde le type d'objet qui est retourné par la méthode Ok, on voit que c'est un OkObjectResult dans le cas où Ok prend une valeur.
Et un objet OkResult lorsqu'il n'y en a pas.