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

Posted

技术标签:

【中文标题】如何使用 AutoMock 模拟 Func<T> 工厂依赖项以返回不同的对象?【英文标题】:How do I mock Func<T> factory dependency to return different objects using AutoMock? 【发布时间】:2022-01-04 08:18:53 【问题描述】:

我正在尝试为一个构造函数依赖于Func&lt;T&gt; 的类编写测试。为了成功完成被测函数,需要创建多个 T 类型的单独对象。

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

下面的测试用例显示了使用 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);

【问题讨论】:

你能告诉我们更多关于你为什么这样做的原因吗?例如,通常 Autofac 从容器中填写Func&lt;T&gt;,而您只需注册T。您是否尝试过创建一个真实的容器,但里面有模拟对象?您可能可以通过以不同的方式进行模拟来解决问题,因此知道为什么可以帮助人们更好地回答您。 感谢您的回复。我更新了我的代码示例,希望能更清楚一点,所以我们有 TestIt 使用 AutoMock 和 TestIt2 直接使用 Autofac。 在这种情况下,我认为模拟对象并不是特别相关(尽管我们真正的测试用例确实包括它们)。我想我们可以在这里直接使用 Autofac - 尽管很高兴了解为什么会发生这种情况,这样我们就可以正确决定将来何时使用 AutoMock/Autofac。 使用 AutoMock 的源代码逐步完成我的测试 - 看起来默认注册是 InstancePerLifetimeScope,而不是 AutoFac 默认的 InstancePerDependency。这肯定会导致我所看到的! 【参考方案1】:

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

但是,您从中获得的模拟将被视为您可能从 Moq 获得的任何其他模拟。当您使用 Moq 设置模拟实例时,除非您自己也实现了工厂逻辑,否则您不会每次都获得一个新实例。

AutoMock 不用于模拟 Autofac 行为。Func&lt;T&gt; 支持 Autofac 在每次调用 Func&lt;T&gt; 时调用解析操作 - 这是 Autofac,而不是 Moq。

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  

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

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

【讨论】:

感谢您提供非常全面的答案!是的,我想我明白现在发生了什么。 我肯定对 Func 的处理位置有误解,所以这真的很有用。而且,是的,我确实错过了为什么。简短的回答 - 我真的不知道为什么会这样!更长的答案,这是一个已经开发了 10 年的产品,autofac 是在开发中引入了几年的。该区域没有很好的测试覆盖率,因此我们在尝试重构之前尝试提高测试覆盖率,同时将更改保持在最低限度。

以上是关于如何使用 AutoMock 模拟 Func<T> 工厂依赖项以返回不同的对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟未按名称调用的函数?

在设计应用程序时如何使用 Func<> 和 Action<>?

如何使用自动映射器映射表达式<func<Entity,DTO>>

C# 如何将 Expression<Func<SomeType>> 转换为 Expression<Func<OtherType>>

如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?

GMock:在模拟函数中捕获引用参数