Injecter des Mocks via IOC (NInject et Rhino Mocks)

Voici une de mes publications (code développé avec Lionel Molas) sur un site de programmation en collaboration avec des amis : http://www.homonerdicus.com/

Le lien vers l’article est celui ci : http://www.homonerdicus.com/?p=17

Attention, ce post s’adresse aux gens qui savent ce qu’est de l’IOC et du Mocking 🙂

Bonne lecture !

 

En voici une version copiée :

Bonjour à tous,

Voici un petit post expliquant l’intégration quasi-parfaite de Rhino Mocks avec NInject. Pour rafraichir les esprits, NInject est un framework d’IOC et donc d’injection de dépendance. Il permet l’injection de classes et autorise donc un fort découplage dans votre application. Rhino Mocks est quand à lui un framework fortement lié au contexte du test. Il permet, lors d’un test unitaire, de “bouchonner” une brique en cours de test afin d’isoler celle ci et donc de garantir sa testabilité “unitaire”.

Le but de cet article est de concevoir une application à faible couplage, avec NInject, et facilement “Mockable” avec RhinoMock. Afin de comprendre cet article, il est fortement conseillé de connaitre le principe de Mock, d’injection et l’utilisation de Rhino Mocks.

Contexte


Dans notre projet de test, NInject est installé coté serveur. Il met en relation les différentes classes en garantissant un fort découplage. Arrive la partie des tests unitaires. Nous décidons de mettre Rhino Mocks afin de bien “unitariser” les tests. Seul problème, les dépendances sont injectées en cours d’exécution. Il faut donc demander à NInject d’injecter du Rhino Mocks.

Les versions utilisés sont Rhino Mocks 3.6 et NInject 2.0

Compréhension par l’exemple :

Exemple de classe métier injectée avec NInject :

public class FileDAO : IFileDAO
 {
     [Inject]
     private IFileDAL _fileDAL { get; set; }

     public virtual File AddFile(string name, FileType fileType, byte[] contentFile)
     {
         //Some stuff here that use _fileDAL
     }

     public virtual bool DeleteFile(long index)
     {
         //Some stuff here that use _fileDAL
     }
 }

Sur le code ci dessus nous remarquons que la variable _fileDAL est injecté par NInject (on le remarque par le tag [Inject]) lors de la création d’une instance de la classe.

public void AuthenticateTest()
{
    //Creation of the mock
    IUserDAO = _userDAOMock = Repository.StrictMock()

    //Usefull data for test
    string login = "toto@toto.com";
    string password = "titi";
    Employee user = new Employee(login, password);

    //Record what will be called and what will be returned
    using (NInjectMockProvider.Repository.Record())
    {
        Expect.Call(_userDAOMock.GetEmployee(login, password)).Return(user);
    }

    //Start the "playback" feature.
    using (NInjectMockProvider.Repository.Playback())
    {
        bool authOK = loginService.Authenticate(login, password);

        //Verify that everithing has been properly called
        _userDAOMock.VerifyAllExpectations();
        //Check if the test is OK
        Assert.IsTrue(authOK);
    }
}

Sur le code ci dessus, nous voyons les quelques étapes essentielles de l’utilisation de Rhino Mocks :

  • Mise en place des données de test
  • Enregistrement du comportement du mock
  • Lancement de la séquence de test
  • Vérification que l’enregistrement correspond bien au comportement appelé et vérification des résultats.

Le but de cet article est donc de s’abstraire de cette ligne :

IUserDAO _userDAOMock = Repository.StrictMock<IUserDAO>();

Comme nous injectons aussi dans nos classes testées, il nous faudrait procéder à l’injection des mocks par NInject.

Don’t fight NInject !


Pour paraméter l’injection, NInject s’appuie sur une classe dérivée de la classe abstraite NinjectModule ou il suffit de surcharger la fonction Load() afin de définir les bindings sous la forme :

Bind<IUserDAO>().To<UserDAO>().InSingletonScope();

Ninject nous donne la possibilité d’ajouter un “Provider” qui fournirait donc l’instance de la classe. L’utilisation parfaite dans notre cas serait :

Bind<IUserDAO>().ToProvider<NInjectMockProvider<IUserDAO>>();

Nous indiquerons alors à NInject d’injecter un Mock pour des IUserDAO. Nous unifions donc le provider NInject avec la factory Rhino Mocks !

Que fait ce provider ?

public class NInjectMockProvider<T> : IProvider where T : class
{
    //References to the MockRepository
    public static MockRepository Repository = new MockRepository();
    //Static dictionnary that allow to create a Mock by kernel
    private static Dictionary<int, object> allItems = new Dictionary<int, object>();

    public object Create(IContext context)
    {
        return CreateInstance(context);
    }

    //Say Ninject what type we are injecting
    public Type Type
    {
        get { return typeof(T); }
    }

    //Create the mock or return the mock already created
    private T CreateInstance(IContext context)
    {
        int temp = context.Kernel.GetHashCode();
        if (!allItems.Keys.Contains(temp))
        {
            allItems.Add(temp, Repository.StrictMock<T>());
    }
    return allItems[temp] as T;
}

Ce qu’il faut retenir de ce provider-factory :

  • Il est configuré en injection Singleton by-design. Nous n’avons pas creusés plus loin, cela couvrait nos besoins
  • Il simule auprès de NInject l’injection d’une classe abstraite
  • Il s’occupe de la création des mocks typés.

Nous pourrions améliorer son fonctionnement en gérant les types d’injections (singleton, par thread, “OnePerRequest”) par exemple.

Références :


Ninject Dojo

Rhino Mocks documentation

Spécial Thanks to :


Lionel Molas, pour le peer-programming qui nous a permit de trouver la solution ainsi que son idée de publier la solution !

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.