如何使用 AutoMock 模拟 Func<T> 工厂对 return 不同对象的依赖?

How do I mock Func<T> factory dependency to return different objects using AutoMock?

我正在尝试为 class 编写一个测试,它的构造函数依赖于 Func<T>。为了成功完成被测函数,需要创建多个 T.

类型的单独对象

当 运行 在生产中时,AutoFac 每次调用 factory() 时都会生成一个新的 T,但是当使用 AutoMock 编写测试时它 returns 相同的对象它再次被调用。

下面的测试用例显示了使用 AutoFac 和 AutoMock 时的行为差异。我希望这两项都能通过,但 AutoMock 失败了。

public class TestClass
{
    private readonly Func<TestDep> factory;

    public TestClass(Func<TestDep> factory)
    {
        this.factory = factory;
    }

    public TestDep Get()
    {
        return factory();
    }
}

public class TestDep
{}

[TestMethod()]
public void TestIt()
{
    using var autoMock = AutoMock.GetStrict();

    var testClass = autoMock.Create<TestClass>();

    var obj1 = testClass.Get();
    var obj2 = testClass.Get();

    Assert.AreNotEqual(obj1, obj2);
}

[TestMethod()]
public void TestIt2()
{
    var builder = new ContainerBuilder();

    builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
            
    var container = builder.Build();

    var testClass = container.Resolve<TestClass>();
            
    var obj1 = testClass.Get();
    var obj2 = testClass.Get();

    Assert.AreNotEqual(obj1, obj2);
}

AutoMock(来自 Autofac.Extras.Moq package)主要用于设置复杂的模拟。也就是说,您有一个具有很多依赖项的对象,并且很难设置该对象,因为它没有无参数构造函数。默认情况下,Moq 不允许您设置带有构造函数参数的对象,因此有一些东西可以填补空白。

但是,您从中获得的模拟与您可能从最小起订量获得的任何其他模拟一样对待。当您使用 Moq 设置模拟实例时,您不会每次都获得一个新实例,除非您自己也实现了工厂逻辑。

AutoMock 不适用于模拟 Autofac 行为。 Func<T> 支持 Autofac 在每次调用 Func<T> 时调用解析操作 - 这就是 Autofac,不是最小起订量。

AutoMock 使用 InstancePerLifetimeScope 是有意义的,因为就像使用普通 Moq 设置模拟一样,您需要能够取回模拟实例以对其进行配置和验证。如果每次都是新的,那就更难了。

显然有一些方法可以解决这个问题,并且通过大量的破坏性更改,您可能可以在其中实现 InstancePerDependency 语义,但此时这样做确实没有太大价值,因为这并不是真正的目的......你总是可以创建两个不同的 AutoMock 实例来获得两个不同的模拟。

一般来说,更好的方法是提供有用的抽象并在容器中使用带有模拟的 Autofac。

例如,假设您有...

public class ThingToTest
{
  public ThingToTest(PackageSender sender) { /* ... */ }
}

public class PackageSender
{
  public PackageSender(AddressChecker checker, DataContext context) { /* ... */ }
}

public class AddressChecker { }

public class DataContext { }

如果您尝试设置 ThingToTest,您会发现设置 PackageSender 会变得很复杂,您可能需要像 AutoMock 这样的东西来处理它.

但是,您可以通过在此处引入一个界面来让您的生活更轻松。

public class ThingToTest
{
  public ThingToTest(IPackageSender sender) { /* ... */ }
}

public interface IPackageSender { }

public class PackageSender : IPackageSender { }

通过隐藏接口背后的所有复杂性,您现在可以仅 IPackageSender 使用普通 Moq(或您喜欢的任何其他模拟框架,甚至创建手动存根实现)进行模拟。您甚至不需要在组合中包含 Autofac,因为您可以直接模拟依赖项并将其传入。

要点是,您可以设计自己的方式来简化测试和设置,这就是为什么在对您的问题的评论中,我问 为什么 您这样做(在撰写本文时,从未得到答复)。如果可能的话,我强烈建议设计一些更容易测试的东西。