简体   繁体   English

单元测试依赖项注入

[英]Unit testing the dependency injection

I am using Autofac for IoC 我正在为IoC使用Autofac

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 这种方法的问题是,我需要将IContainerFactory传递给我在应用程序中使用的每个类的构造函数,如下所示

  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? 如何提出一个好的解决方案?

A better approach would be to pass the dependencies you need in your class into your constructor: 更好的方法是将类中需要的依赖项传递给构造函数:

public class MainClass : IMainClass
{       
    private readonly ILogger _logger;
    private readonly IDatabase _db;

    public MainClass(ILogger logger, IDatabase db)
    {
        _logger = logger;  
        _db = db;
    }

    public void AddDetails(Data data)
    {
        //do some business operations 
        _db.Add(data);
        _logger.Information("added");
    }
}

Then you could use a mocking framework such as Moq to mock your class dependencies and perform verifications on whether the dependencies were called: 然后,您可以使用诸如Moq之类的模拟框架来模拟您的类依赖关系,并验证是否调用了依赖关系:

[TestClass]
public class UnitTest1
{
    private Mock<ILogger> _mockLogger = new Mock<ILogger>();
    private Mock<IDatabase> _mockDb = new Mock<IDatabase>();

    [TestMethod]
    public void TestMethod1()
    {
        // arrange
        var mainClass = new MainClass(_mockLogger.Object, _mockDb.Object);
        var data = new Data();

        // act
        mainClass.AddDetails(data);

        // assert    
        _mockDb
            .Verify(v => v.Add(data), Times.Once);
    }
}

I would not verify your log message though as this could change and make the test brittle. 我不会验证您的日志消息,因为这可能会改变并使测试变脆。 Only verify functionality which is essential to doing what the method is intended for. 仅验证对执行该方法所必需的功能。

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 应该重构MainClass以遵循显式依赖原则

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 . 对于依赖于容器工厂的任何其他类,也应遵循相同的模式,例如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. 测试MainClass将要求您仅模拟被测类的必要依赖关系。

[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);
    }
}

Here I would like to share solution, which I use in my project 在这里我想分享我在项目中使用的解决方案

To do unit testing of particular function, I use below structure 为了进行特定功能的单元测试,我使用以下结构

[TestClass]
public class TestSomeFunction
{
    public IComponentContext ComponentContext { get; set; }       

    [TestInitialize]
    public void Initialize()
    {
       //Registering all dependencies required for unit testing
       this.ComponentContext = builder.Build(); //You have not build your container in your question
    }

    [TestMethod]
    public void Testfunction()
    {
       //Resolve perticular dependency
       var _logger = containerFactory.Resolve<ILogger>();   
       //Test my function
       //use _logger 
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM