Category Archives: Tests unitaires

Injecter automatiquement des fakes dans une test fixture avec FakeItEasy

Aujourd’hui j’aimerais partager avec vous une fonctionnalité sympa de FakeItEasy que j’ai découverte récemment.

Quand on écrit des tests unitaires pour une classe qui a des dépendances, on a généralement besoin de créer des objets factices (fakes) pour les dépendances et de les injecter manuellement dans la classe testée (SUT, System Under Test), ou d’utiliser un conteneur IoC pour enregistrer les dépendances factices et construire le SUT. C’est un peu laborieux, et il y a quelques mois j’avais créé une extension Unity pour créer automatiquement les fakes et rendre tout ça plus facile. Mais maintenant je réalise que FakeItEasy offre une solution encore meilleure : déclarer simplement les dépendances et le SUT comme des propriétés de la fixture, et appeler Fake.InitializeFixture sur la fixture pour les initialiser. Voilà à quoi ça ressemble :

public class BlogManagerTests
{
    [Fake] public IBlogPostRepository BlogPostRepository { get; set; }
    [Fake] public ISocialNetworkNotifier SocialNetworkNotifier { get; set; }
    [Fake] public ITimeService TimeService { get; set; }
 
    [UnderTest] public BlogManager BlogManager { get; set; }
 
    public BlogManagerTests()
    {
        Fake.InitializeFixture(this);
    }
 
    [Fact]
    public void NewPost_should_add_blog_post_to_repository()
    {
        var post = A.Dummy<BlogPost>();
 
        BlogManager.NewPost(post);
 
        A.CallTo(() => BlogPostRepository.Add(post)).MustHaveHappened();
    }
 
    [Fact]
    public void PublishPost_should_update_post_in_repository_and_publish_link_to_social_networks()
    {
        var publishDate = DateTimeOffset.Now;
        A.CallTo(() => TimeService.Now).Returns(publishDate);
 
        var post = A.Dummy<BlogPost>();
 
        BlogManager.PublishPost(post);
 
        Assert.Equal(BlogPostStatus.Published, post.Status);
        Assert.Equal(publishDate, post.PublishDate);
 
        A.CallTo(() => BlogPostRepository.Update(post)).MustHaveHappened();
        A.CallTo(() => SocialNetworkNotifier.PublishLink(post)).MustHaveHappened();
    }
}

Le SUT est déclaré comme une propriété avec l’attribut [UnderTest]. Chaque dépendance qu’on a besoin de manipuler est déclarée comme une propriété avec l’attribut [Fake]. La méthode Fake.InitializeFixture initialise le SUT, en créant les dépendances factices à la volée, et affecte ces dépendances aux propriétés correspondantes.

J’aime beaucoup le fait que les tests semblent beaucoup plus propres avec cette technique ; le code de plomberie est réduit au strict minimum, il ne reste plus qu’à configurer les dépendances et écrire les tests proprement dits.

Quelques remarques sur le code ci-dessus :

  • On peut utiliser des champs privés plutôt que des propriétés publiques pour les fakes et le SUT, mais comme ces champs ne sont jamais explicitement assignés dans le code, cela provoque un avertissement du compilateur (CS0649), donc je préfère utiliser des propriétés.
  • Les tests dans mon exemple utilisent xUnit, j’ai donc mis l’appel à Fake.InitializeFixture dans le constructeur, mais si vous utilisez un autre framework comme NUnit ou MSTest, vous pouvez le mettre dans la méthode Setup.

Notez aussi qu’il y a des limites aux scénarios supportés par cette approche :

  • seule l’injection par constructeur est supportée, pas l’injection par propriétés (autrement dit les dépendances doivent être des paramètres du constructeur du SUT)
  • les dépendances nommées ne sont pas supportées ; seul le type est pris en compte, on ne peut donc pas avoir plusieurs dépendances distinctes du même type
  • les collections de dépendances ne sont pas supportées (c’est à dire si votre SUT reçoit une collection de dépendances, par exemple IFooService[])

Créer un conteneur qui crée automatiquement des objets factices avec Unity et FakeItEasy

L’écriture de tests unitaires peut parfois être un peu rébarbative, surtout quand on teste des classes qui ont des dépendances complexes. Heureusement, certains outils rendent cela un peu plus facile. J’utilise beaucoup FakeItEasy ces derniers temps ; c’est un framework de mock pour .NET très facile à utiliser. Il a une API simple et élégante, basée sur les génériques et les expressions lambda, et c’est un vrai plaisir de l’utiliser. Ça a été une bouffée d’air frais par rapport au vieux RhinoMocks que j’utilisais jusqu’ici.

Mais malgré toutes les qualités de FakeItEasy, l’enregistrement des fakes (objets factices) pour les dépendances de la classe testée est encore un peu laborieuse. Ce serait bien si le conteneur IoC pouvait nous créer automatiquement des fakes à la volée, non ? Le code suivant :

var container = new UnityContainer();
 
// Setup dependencies
var fooProvider = A.Fake<IFooProvider>();
container.RegisterInstance(fooProvider);
var barService = A.Fake<IBarService>();
container.RegisterInstance(barService);
var bazManager = A.Fake<IBazManager>();
container.RegisterInstance(bazManager);
 
var sut = container.Resolve<SystemUnderTest>();

Pourrait être réduit à ceci :

var container = new UnityContainer();
 
// This will cause the container to provide fakes for all dependencies
container.AddNewExtension<AutoFakeExtension>();
 
var sut = container.Resolve<SystemUnderTest>();

Eh bien, c’est en fait assez facile à faire avec Unity. Unity n’est pas vraiment le conteneur IoC le plus à la mode, mais il est bien supporté, facile à utiliser, et extensible. J’ai créé l’extension suivante pour permettre le scénario décrit plus haut :

public class AutoFakeExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        Context.Strategies.AddNew<AutoFakeBuilderStrategy>(UnityBuildStage.PreCreation);
    }
     
    private class AutoFakeBuilderStrategy : BuilderStrategy
    {
        private static readonly MethodInfo _fakeGenericDefinition;
     
        static AutoFakeBuilderStrategy()
        {
            _fakeGenericDefinition = typeof(A).GetMethod("Fake", Type.EmptyTypes);
        }
         
        public override void PreBuildUp(IBuilderContext context)
        {
            if (context.Existing == null)
            {
                var type = context.BuildKey.Type;
                if (type.IsInterface || type.IsAbstract)
                {
                    var fakeMethod = _fakeGenericDefinition.MakeGenericMethod(type);
                    var fake = fakeMethod.Invoke(null, new object[0]);
                    context.PersistentPolicies.Set<ILifetimePolicy>(new ContainerControlledLifetimeManager(), context.BuildKey);
                    context.Existing = fake;
                    context.BuildComplete = true;
                }
            }
            base.PreBuildUp(context);
        }
    }
}

Quelques commentaires sur ce code :

  • La logique est un peu rudimentaire (on génère un fake uniquement pour les interfaces et classes abstraites), mais elle peut facilement être ajustée selon les besoins.
  • Le vilain hack de réflexion est dû au fait que FakeItEasy n’a pas de surcharge non générique pour la méthode A.Fake qui accepte un Type en paramètre. Nul n’est parfait…
  • La durée de vie du fake est définie à “container controlled” (c’est-à-dire qu’elle a la durée de vie du conteneur), parce que si on a besoin de configurer les appels sur le fake, il faut qu’on puisse accéder à la même instance que celle qui est injectée dans la classe testée :
var fooProvider = container.Resolve<IFooProvider>();
A.CallTo(() => fooProvider.GetFoo(42)).Returns(new Foo { Id = 42, Name = “test” });

Remarquez que si on enregistre explicitement une dépendance dans le conteneur, elle sera utilisée en priorité et aucun fake ne sera créé. On peut donc utiliser cette extension tout en gardant la possibilité de spécifier certaines dépendances manuellement :

var container = new UnityContainer();
container.AddNewExtension<AutoFakeExtension>();
container.RegisterType<IFooProvider, TestFooProvider>();
var sut = container.Resolve<SystemUnderTest>();

Bien sûr, cette extension pourrait facilement être modifiée pour utiliser un framework de mock autre que FakeItEasy. Je suppose que le même principe pourrait aussi être appliqué à d’autres conteneurs IoC, pourvu qu’ils aient des points d’extension appropriés.

Et AutoFixture dans tout ça ?

Avant que vous ne posiez la question : oui, je connais AutoFixture. C’est une très bonne librairie, et j’ai aussi essayé de l’utiliser, avec un certain succès. Le code est d’ailleurs très similaire aux exemples ci-dessus. La principale raison pour laquelle je n’ai pas continué à l’utiliser est qu’AutoFixture n’est pas vraiment un conteneur IoC (bien qu’il fasse certaines des choses que fait un conteneur IoC), et je préfère utiliser le même mécanisme d’injection de dépendance dans mes tests unitaires que dans le code applicatif. De plus, je n’étais pas très à l’aise avec la façon dont AutoFixture gère les propriétés quand il crée une instance de la classe à tester. Par défaut, il initialise toutes les propriétés publiques avec des fakes de leur type ; c’est très bien dans le cas des propriétés utilisées pour l’injection de dépendance, mais ça n’a pas de sens pour les autres propriétés. Je sais qu’il est possible d’inhiber ce comportement pour des propriétés spécifiques, mais il faut le faire manuellement au cas par cas ; cela ne prend pas en compte les attributs spécifiques au conteneur IoC, comme [Dependency]. Au final, j’ai trouvé plus simple d’utiliser mon extension Unity personnalisée, dont je comprends et maitrise bien le comportement.

Test unitaires asynchrones avec NUnit

Récemment, mon équipe et moi avons commencé à écrire des tests unitaires pour une application qui utilise beaucoup de code asynchrone. Nous avons utilisé NUnit (2.6) parce que nous le connaissions déjà bien, mais nous ne l’avions encore jamais utilisé pour tester du code asynchrone.

Supposons que le système à tester soit cette très intéressante classe Calculator :

    public class Calculator
    {
        public async Task<int> AddAsync(int x, int y)
        {
            // simulate long calculation
            await Task.Delay(100).ConfigureAwait(false);
            // the answer to life, the universe and everything.
            return 42;
        }
    }

(Indice: ce code contient un bug… 42 n’est pas toujours la réponse. Ça m’a fait un choc quand j’ai appris ça!)

Et voici un test unitaire pour la méthode AddAsync :

        [Test]
        public async void AddAsync_Returns_The_Sum_Of_X_And_Y()
        {
            var calculator = new Calculator();
            int result = await calculator.AddAsync(1, 1);
            Assert.AreEqual(2, result);
        }

async void vs. async Task

Avant même de lancer ce test, je me suis dit : Ça ne va pas marcher! une méthode async void va retourner immédiatement sur le premier await, NUnit va donc croire que le test est terminé alors que l’assertion n’a pas été exécutée, et le test va donc passer même si l’assertion échoue. J’ai donc changé la signature de la méthode en async Task, en me croyant très malin d’avoir évité ce piège…

        [Test]
        public async Task AddAsync_Returns_The_Sum_Of_X_And_Y()

Comme prévu, le test a échoué, ce qui confirme que NUnit sait gérer les tests asynchrones. J’ai corrigé la classe Calculator, et je n’y ai plus pensé. Jusqu’au jour où j’ai remarqué qu’un collègue écrivait ses tests avec async void. J’ai donc commencé à lui expliquer pourquoi ça ne pouvait pas marcher, et j’ai essayé de le lui démontrer en ajoutant une assertion qui échouerait… et à ma grande surprise, le test a échoué, prouvant que j’avais tort !

Etant d’une nature curieuse, j’ai aussitôt commencé à investiguer… Ma première idée a été de vérifier le SynchronizationContext courant, et en effet, j’ai vu que NUnit l’avant remplacé par une instance de NUnit.Framework.AsyncSynchronizationContext. Cette classe maintient une file des continuations qui sont postées dessus. Après que la méthode async void retourne (c’est-à-dire la première fois qu’on await une tâche qui n’est pas encore terminée), NUnit appelle la méthode WaitForPendingOperationsToComplete, qui exécute toute les continuations de la file, jusqu’à ce que celle-ci soit vide. C’est seulement là que le test sera considéré comme terminé.

La morale de cette histoire est donc que vous pouvez écrire des tests async void avec NUnit 2.6. Cela fonctionne aussi avec les delegates passés à Assert.Throws, qui peuvent avoir le modificateur async. Cela étant dit, ce n’est pas parce que vous pouvez le faire que c’est forcément une bonne idée… Les frameworks de test unitaire n’ont pas tous le même support pour cela, et la prochaine version de NUnit (la 3.0, encore en alpha), ne supportera pas les tests async void.

Donc, à moins que vous ne comptiez rester sur NUnit 2.6.4 ad vitam æternam, il vaut probablement toujours utiliser async Task dans vos tests unitaires.