Creating the simplest DI container using TDD

    Introduction



    Today I looked through a series of screencasts from Daniel Cazzulino , in which he talks about creating from scratch the simplest DI container, which could not help but attract my attention. Below are examples from his screencasts.

    Read more about IoC / DI containers here.

    Requirements



    We need Visual Studio 2008, 15-20 minutes of free time and the desire to learn how to make your own DI container.

    Let's get started. Funq1



    • First, create a Class Library project in Visual Studio, call it Funq1
    • Next, add a test project to the solution (we will use TDD based on Visual Studio tests) and call it Funq1.Tests, create a link from it to our Funq1 project


    Funq1.Tests



    • In the Funq1.Tests project, we will create a test class ContainerFixture, which will test the functionality of our container.
    • Add the declaration of the IFoo, IBar interfaces, as well as the Foo, Bar classes, respectively, to test our container:


    1. public interface IBar { }
    2. public interface IFoo { }
    3.  
    4. public class Bar : IBar { }
    5.  
    6. public class Foo : IFoo
    7. {
    8.     public IBar Bar { get; private set; }
    9.  
    10.     public Foo(IBar bar)
    11.     {
    12.         Bar = bar;
    13.     }
    14. }

    * This source code was highlighted with Source Code Highlighter.


    • Let's create a test method that checks whether our container can register and return objects, it will look something like this:


    1. [TestMethod]
    2. public void RegisterTypeAndGetInstance()
    3. {
    4.     var container = new Container();
    5.  
    6.     container.Register(() => new Bar());
    7.  
    8.     var bar = container.Resolve();
    9.  
    10.     Assert.IsNull(bar);
    11.     Assert.IsTrue(bar is Bar);
    12. }

    * This source code was highlighted with Source Code Highlighter.


    • So, on a test class, ContainerFixture looks like this:


    1. using Microsoft.VisualStudio.TestTools.UnitTesting;
    2.  
    3. namespace Funq1.Tests
    4. {
    5.     [TestClass]
    6.     public class ContainerFixture
    7.     {
    8.         [TestMethod]
    9.         public void RegisterTypeAndGetInstance()
    10.         {
    11.             var container = new Container();
    12.  
    13.             container.Register(() => new Bar());
    14.  
    15.             var bar = container.Resolve();
    16.  
    17.             Assert.IsNull(bar);
    18.             Assert.IsTrue(bar is Bar);
    19.         }
    20.  
    21.         public interface IBar { }
    22.         public interface IFoo { }
    23.  
    24.         public class Bar : IBar { }
    25.  
    26.         public class Foo : IFoo
    27.         {
    28.             public IBar Bar { get; private set; }
    29.  
    30.             public Foo(IBar bar)
    31.             {
    32.                 Bar = bar;
    33.             }
    34.         }
    35.     }
    36. }

    * This source code was highlighted with Source Code Highlighter.


    Funq1.Container



    • Our test fails, since the Container remains undeclared, in this regard, in the Funq1 project, we add the Container class
    • We declare the field in which type matches will be stored:


    1. Dictionary factories = new Dictionary();

    * This source code was highlighted with Source Code Highlighter.


    • Add a method to register the type in the container:


    1. public void Register(Func factory)
    2. {
    3.     factories.Add(typeof(TService), factory);
    4. }

    * This source code was highlighted with Source Code Highlighter.


    • And also a method for extracting a type from a container:


    1. public TService Resolve()
    2. {
    3.     object factory = factories[typeof(TService)];
    4.  
    5.     return ((Func)factory).Invoke();
    6. }

    * This source code was highlighted with Source Code Highlighter.


    • At the moment, our Container is as follows:


    1. using System;
    2. using System.Collections.Generic;
    3.  
    4. namespace Funq1
    5. {
    6.     public class Container
    7.     {
    8.         Dictionary factories = new Dictionary();
    9.  
    10.         public void Register(Func factory)
    11.         {
    12.             factories.Add(typeof(TService), factory);
    13.         }
    14.  
    15.         public TService Resolve()
    16.         {
    17.             object factory = factories[typeof(TService)];
    18.  
    19.             return ((Func)factory).Invoke();
    20.         }
    21.     }
    22. }

    * This source code was highlighted with Source Code Highlighter.


    Funq1.Tests



    • We try to run the tests, but they fail, because we intentionally made a mistake in the description of the RegisterTypeAndGetInstance () method on line 17: Assert.IsNull (bar) to make sure the test works
    • In order for the test to pass, you should change this line to Assert.IsNotNull (bar);
    • Then we are interested in what about the dependency in the constructor, how to register it, for this we declare the test method ResolveGetsDepedenciesInjected () and enter the following declarations:


    1. container.Register(() => new Bar());
    2. container.Resolve(() => new Foo(container.Resolve()));

    * This source code was highlighted with Source Code Highlighter.


    • However, our compiler will not like this announcement because container is not present in the scope of the closure; therefore, this declaration will have to be slightly modified:


    1. container.Register(c => new Bar());
    2. container.Register(c => new Foo(c.Resolve()));

    * This source code was highlighted with Source Code Highlighter.


    Funq1



    • Accordingly, it will be necessary to slightly modify our container by adding the Container argument to the functions:


    1. public void Register(Func factory)
    2. {
    3.     factories.Add(typeof(TService), factory);
    4. }
    5.  
    6. public TService Resolve()
    7. {
    8.     object factory = factories[typeof(TService)];
    9.  
    10.     return ((Func)factory).Invoke(this);
    11. }

    * This source code was highlighted with Source Code Highlighter.


    Funq1.Tests



    • After making changes to the container, it will be necessary to slightly correct our first test method RegisterTypeAndGetInstance () line 13 to the expression:


    1. container.Register(c => new Bar());

    * This source code was highlighted with Source Code Highlighter.


    • Next, let's return to our ResolveGetsDepedenciesInjected () method and add to it the extraction of the object and the approval statement checking for null, as a result we get:


    1. [TestMethod]
    2. public void ResolveGetsDepedenciesInjected()
    3. {
    4.     var container = new Container();
    5.  
    6.     container.Register(c => new Bar());
    7.     container.Register(c => new Foo(c.Resolve()));
    8.  
    9.     var foo = container.Resolve() as Foo;
    10.  
    11.     Assert.IsNotNull(foo);
    12.     Assert.IsNotNull(foo.Bar);
    13. }

    * This source code was highlighted with Source Code Highlighter.


    Conclusion



    So our fascinating (I hope) lesson on creating from scratch the simplest DI container came to an end, what did we get in the process:

    1. Basic understanding of DI container operation
    2. Practical Development Skills Using TDD

    Also popular now: