Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Xamarin.forms Unit Testing avec MOQ

On ne se moque pas ! Les tests c’est sérieux. Et surtout lorsqu’il s’agit de tester avec précision un code faisant appel à d’autres codes. Bouchonner, voilà l’astuce, mais avec quoi ?

MOQ

Introduction

Lorsque l’on doit écrire des tests unitaires, il est souvent très pratique de pouvoir simuler le comportement de certains objets pour travailler dans un contexte isolé.

Par exemple simuler un « repository » d’objets habituellement liés à la base de données avec un « repository » factice ne retournant qu’un objet bien défini ou encore simuler les réponses d’un service Web sans même devoir le contacter.

Il est aussi important d’utiliser des objets factices pour conserver le caractère de répétabilité d’un test (une propriété de date ou un capteur de température devant rester figés pour obtenir les mêmes résultats en sortie et donc la validation du test).

Le faire à la main ou utiliser une toolbox

Bien sûr il est souvent facile de créer soi-même ce type d’objet factice (que l’on pourrait nommer « stub » ou encore « bouchon » d’où le verbe et l’expression « bouchonner » un code), mais pour nous faciliter le travail il existe de nombreuses toolbox dédiées à cette tâche dite du « mocking ».

Un mock est un remplacement orienté test d’un objet. Il possède en plus d’un « stub » classique la capacité de se paramétrer à l’exécution (techniquement il génère souvent un proxy dynamique) et de vérifier qu’il est utilisé comme attendu.

Le mock est comparable au mannequin utilisé dans les crashs tests de véhicules par exemple. C’est un objet factice qui présente des propriétés pertinentes dans le cadre du test (ici une taille et une masse comparable à celles des êtres humains).

Il faut bien comprendre que ce n’est pas le mock qui est la cible du test (dans le cas précédent c’est le véhicule l’objet final du test, pas le mannequin qui est néanmoins très important dans le recueil des données). Le mock ne sert qu’à borner précisément un contexte pour permettre une analyse plus facile des objets « réels » à tester. Les Mocks automatisés de MOQ évitent de trop taper de code, car sinon qui testera le code du testeur ? !

Pour réaliser un premier mock, je vous propose d’utiliser la Toolbox Moq qui se trouve intégré aux projets de tests unitaires de COL et COBALT et que vous pouvez donc utiliser directement.

Moq est une toolbox simple mais puissante qui repose intégralement sur les lambdas expressions introduites avec le C# 3 pour son paramétrage. Ceci nous assure une approche « type-safe » et la capacité de pouvoir faire du refactoring de base du code original sans retoucher au code de test.

Un exemple d’utilisation :

public interface IBanque
{
  event EventHandler CasseDuSiecle;
  string Nom { get; set; }
  double ObtenirSoldeCompte(int idCompte);
}

var mock = new Mock<IBanque>();

// lire "dès que la propriété 'Nom' est accédée en lecture,
// – retourner Banque Picsou"
// - et effectuer le code de callback"
// (on aurait pu indiquer une méthode au lieu d'une lambda)

mock.SetupGet(b => b.Nom)
           .Returns("Banque Picsou")
           .Callback(() => Console.WriteLine("callback du mock"));

// lire "dès que la fonction 'ObtenirSoldeCompte' est appellée,
// - pour n'importe qu'elle valeur de id, retourne 15000
// (on aurait pu indiquer une valeur fixe, un regex ou un range)
// - et lance l'évènement CasseDuSiecle"

mock.Setup(b => b.ObtenirSoldeCompte(It.IsAny<int>()))
     .Returns(15000.0)
     .Raises(b => b.CasseDuSiecle += null, null, EventArgs.Empty);

// On obtient notre instance 'factice' de IBanque

IBanque banque = mock.Object;

// Imaginons l'utilisation du mock par des objets réels:

banque.CasseDuSiecle += new EventHandler(
      (s, a) => Console.WriteLine("C'est le casse du siècle!"));

Console.WriteLine(banque.Nom);
Console.WriteLine(banque.ObtenirSoldeCompte(45654));

// Retour à notre contexte de test, vérifions que l'appel à
// 'Nom' a bien été effectué exactement une fois

mock.VerifyGet(b => b.Nom, Times.Once());

Résultat:

callback du mock

Banque Picsou

C'est le casse du siècle!

15000

Techniquement il est possible de mocker des interfaces, des classes abstraites ou concrètes pour peu que les méthodes soient virtuelles. Il est également possible de faire du mock récursif, par exemple en mockant une propriété d’une propriété de l’objet principal, directement dans la lambda expression de plus haut niveau. Le mieux restant de partir d’interfaces.

L'avantage, c'est que pour les habitués des lambdas expressions, l’apprentissage de Moq reste très simple. Et pour les autres il s’agira d’un bon exercice !

Conclusion

Beaucoup de logiciels utilisent l’injection de dépendances, voire même encore MEF avec WPF. Reprendre des bouts de codes des services utilisés pour fabriquer un contexte de test est une hérésie… On n’utilise pas le code à tester pour faire les tests.

Mais par quoi remplacer les codes utilisés ?

Par des mocks !

Et comment ne pas perdre des heures à écrire un code de mocking qu’il faudra alors lui-même tester, créant ainsi une spirale infernale ?

En utilisant une toolbox comme MOQ !

Références

Pour aller plus loin il est évident qu’il faut se plonger un peu plus dans la documentation de MOQ.

On la trouvera ici avec un Quickstart bien utile : https://github.com/Moq/moq4/wiki/Quickstart

MOQ se trouve disponible en paquet Nuget intégrable à tout projet et son code est visible à cet endroit : https://github.com/moq/moq4