Search code examples
c#unit-testingdependency-injectioninversion-of-control

Unit testing the dependency injection


I am using Autofac for IoC

Here is my container initiator class, which the responsibility is to register the dependencies.

 public class ContainerInit
 {
      public static IContainer BuildContainer()
      {
            var conFac = new ContainerFactory();
            var builder = new ContainerBuilder();
            builder.Register(conFac).As<IContainerFactory>().SingleInstance();
            builder.Register(c=> new MainClass(conFac)).As<IMainClass>().SingleInstance();
            builder.Register(c=> new Database(conFac)).As<IDatabase>().SingleInstance();
             var logger = LoggUtil.CreateLogger();
            builder.Register(logger).As<ILogger>().SingleInstance();

            var container = builder.Build();
            ContainerFactory.SetContainer(container);
            return container;
      }
 }

Problem with this approach is, I need to pass IContainerFactory to the constructor of every class I use in my application as follow

  public class MainClass: IMainClass
  {       
      private readonly ILogger _logger;
      private readonly IDatabase _db;
      public MainClass(IContainerFactory containerFactory)
      {
              _logger = containerFactory.GetInstance<ILogger>();  
              _db =  containerFactory.GetInstance<IDatabase>(); //example       
      }
      public AddDetails(Data data)
      {
        //do some business operations 
        _db.Add(data);
        _logger.Information("added");
      }
  }

So it is difficult to unit test these classes.

How can come up with a good solution?


Solution

  • Your current Service Locator Anti-Pattern is what makes your code difficult to test in isolation as well as makes the class misleading about what it actually depends on.

    MainClass should be refactored to follow Explicit Dependencies Principle

    public class MainClass : IMainClass  
        private readonly ILogger logger;
        private readonly IDatabase db;
    
        public MainClass(ILogger logger, IDatabase db) {
            this.logger = logger;  
            this.db = db;
        }
    
        public void AddDetails(Data data) {
            //do some business operations 
            db.Add(data);
            logger.Information("added");
        }
    }
    

    The same pattern should also be followed for any other class you have that depends on the container factory, like Database.

    You would however need to also refactor the container registration accordingly

    public class ContainerInit {
        public static IContainer BuildContainer() {
            var builder = new ContainerBuilder();
            builder.RegisterType<MainClass>().As<IMainClass>().SingleInstance();
            builder.RegisterType<Database>().As<IDatabase>().SingleInstance();
            var logger = LoggUtil.CreateLogger();
            builder.Register(logger).As<ILogger>().SingleInstance();
    
            var container = builder.Build();
            return container;
        }
    }
    

    Testing MainClass would required you to mock only the necessary dependencies of the class under test.

    [TestClass]
    public class MainClassTests {    
        [TestMethod]
        public void Should_AddDetails_To_Database() {
            // Arrange
            var mockDb = new Mock<IDatabase>();
            var data = new Data();
            var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);
    
            // Act
            mainClass.AddDetails(data);
    
            // Assert    
            mockDb.Verify(_ => _.Add(data), Times.Once);
        }
    }