我如何对使用IoC容器类的类进行单元测试

时间:2017-06-05 04:51:26

标签: c# unit-testing autofac

我在我的项目中使用Autofac,但我无法对某个特定类进行单元测试。

考虑以下场景:

//Class to be tested
public Class A
{
  private SomeAutoFacClass B;

  public void DoSomething()
  {
    B = scope.Resolve<ClassName>();// Resolve the object needed 
    // Do something with instance B
  }
}

// Test class
public Class ATest
{
  private A a;

  [test]
  public void TestMethod()
  {
    a.DoSomething();//*This method causes a null reference exception as it tries to resolve the objects*
  }
}

在上面的代码中,由于依赖注入只能特定于该特定类,因此无法对单元测试用例进行单元测试。 我该如何解决这个问题?我还尝试使用Moq创建一个autofaccontainer。 但那也失败了。

2 个答案:

答案 0 :(得分:6)

您无法测试课程的原因是您的课程依赖于您的DI容器。这是Service Locator anti-pattern的实现。这是一种反模式,因为:

  

Service Locator的问题在于它隐藏了一个类的依赖项,导致运行时错误而不是编译时错误,以及使代码更难维护,因为它变得不清楚你什么时候会引入一个重大改变。

相反,围绕

设计你的课程
  • 构造函数注入,以防该类是一个组件(包含应用程序行为的类),其中您通过构造函数注入类直接需要的依赖项
  • 方法注入当类是一个像实体一样的以数据为中心的对象时,这意味着依赖项被提供给这样的类的公共方法,其中消费类只使用该依赖项,但是不存储它。

组件由您的DI容器构建并在Composition Root中注册,而以数据为中心的对象在组合根之外的代码中被new编辑。在这种情况下,您需要将依赖项传递给已构造的对象。

如果您构建并测试组件,您的代码通常如下所示:

public class ComponentA
{
    private ClassName b;

    public ComponentA(ClassName b)
    {
        this.b = b;
    }

    public void DoSomething()
    {
        // Do something with instance B
    }
}

// Test class
public Class ATest
{
    [test]
    public void TestMethod()
    {
        // Arrange
        B b = new FakeB();

        var a = new ComponentA(b);

        // Act
        a.DoSomething();

        // Assert
        // Check whether be was invoked correctly.
    }
}

如果您构建并测试需要依赖其某个操作的以数据为中心的对象,则代码通常如下所示:

public class EntityA
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void DoSomething(ClassName b)
    {
        // Do something with instance B
    }
}

// Test class
public Class ATest
{
    [test]
    public void TestMethod()
    {
        // Arrange
        B b = new FakeB();

        var a = new EntityA { Name = "Bert", Age = 56 };

        // Act
        a.DoSomething(b);

        // Assert
        // Check whether be was invoked correctly.
    }
}

所以回答你的初步问题:

  

如何对使用IoC容器类的类进行单元测试

你没有。您的应用程序代码不应该依赖于DI容器,因为这会导致各种复杂情况,例如难以测试。

答案 1 :(得分:-2)

使用IoC容器,您应该使用IoC。通常,这是构造函数注入或属性注入,具体取决于容器可以支持的自动注入。

我使用的模式我称之为“懒惰属性注入”,其中构造函数 - 注入我的容器充当注册表,然后在属性使用上使用惰性解决方案..

它看起来像什么:

private readonly IoCContainer _container = null;

private IMyService _myService = null;
public IMyService MyService
{
   get { return _myService ?? (_myService = _container.Resolve<IMyService>()); }
   set { _myService = value; }
}

public MyClass( IoCContainer container)
{ 
   if (container == null)
      throw new ArgumentNullException("container");

   _container = container;
}

现在,当您去测试此类的方法时,您的测试会使用Mock初始化您的MyService。在测试中运行时,我将IoCContainer初始化为Mock(),如果有的话,抛出.Resolve&lt;&gt;打电话。这样可以捕获可能会修改测试中的代码以使用尚未模拟的新依赖项的情况。

这种方法的好处是通常一个类将有几个单一用途的方法,并且通过扩展需要许多依赖项。在像Web请求这样的应用程序中,可能只调用一个方法,需要一个依赖项,依赖项解析只从容器中检索所需的依赖项,而不是所有依赖项。 (即如果你使用带有8个依赖项的构造函数注入,则所有8个都需要在运行时解析,即使只使用1个。)这也简化了单元测试,只模拟了你知道的所需内容而不是一切。