如何使用 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<T>
的类编写测试。为了成功完成被测函数,需要创建多个 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<T>
,而您只需注册T
。您是否尝试过创建一个真实的容器,但里面有模拟对象?您可能可以通过以不同的方式进行模拟来解决问题,因此知道为什么可以帮助人们更好地回答您。
感谢您的回复。我更新了我的代码示例,希望能更清楚一点,所以我们有 TestIt
使用 AutoMock 和 TestIt2
直接使用 Autofac。
在这种情况下,我认为模拟对象并不是特别相关(尽管我们真正的测试用例确实包括它们)。我想我们可以在这里直接使用 Autofac - 尽管很高兴了解为什么会发生这种情况,这样我们就可以正确决定将来何时使用 AutoMock/Autofac。
使用 AutoMock 的源代码逐步完成我的测试 - 看起来默认注册是 InstancePerLifetimeScope,而不是 AutoFac 默认的 InstancePerDependency。这肯定会导致我所看到的!
【参考方案1】:
AutoMock(来自Autofac.Extras.Moq package)主要用于设置复杂的模拟。也就是说,您有一个具有很多依赖项的对象,并且很难设置该对象,因为它没有无参数的构造函数。 Moq 默认情况下不允许您使用构造函数参数设置对象,因此有一些可以填补空白的东西很有用。
但是,您从中获得的模拟将被视为您可能从 Moq 获得的任何其他模拟。当您使用 Moq 设置模拟实例时,除非您自己也实现了工厂逻辑,否则您不会每次都获得一个新实例。
AutoMock 不用于模拟 Autofac 行为。Func<T>
支持 Autofac 在每次调用 Func<T>
时调用解析操作 - 这是 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>>