1

我有一个模拟。这个模拟有两个方法,MethodA()MethodB()。我想将这两种方法都设置为返回false。我创建了各种版本的代码,它们都应该可以工作,但有些不能:

这些工作:

1.

var mock = fixture.Freeze<Mock<MyInterface>>();
mock
    .Setup(m => m.MethodA(It.IsAny<T>(), It.IsAny<T>()))
    .ReturnsAsync(false);
mock
    .Setup(m => m.MethodB(It.IsAny<T>(), It.IsAny<T>()))
    .ReturnsAsync(false);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

2.

var mock = new Mock<MyInterface>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

3.

var mock = new Mock<MyInterface>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock.Object);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

这些不会:

4.

var mock = fixture.Freeze<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

5.

var mock = fixture.Create<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

6.

var mock = fixture.Create<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock.Object);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

根据结果​​,罪魁祸首似乎是Fixture.Create()方法*。出于某种原因,如果模拟是使用fixture.Create()而不是new关键字创建的,它不会保留我使用SetReturnsDefault()设置的配置,即使模拟被冻结(意味着Fixture.Inject()被调用它)。有人可以解释为什么吗?


脚注:

*当您调用Fixture.Freeze()时, Fixture.Create()也会在内部调用- 冻结只是调用Fixture.Create()后跟Fixture.Inject()的简写

因此,这两个片段是等价的:

var mock = fixture.Freeze<Mock<MyInterface>>();

-

var mock = fixture.Create<Mock<MyInterface>>();
fixture.Inject(mock);
4

1 回答 1

1

由于您分享了您正在使用AutoMoqCustomization,它解释了您看到的行为,尽管这是由于两者的一些内部因素Moq以及AutoFixture您可能意想不到的交互方式。

为了完整起见,这是您在问题中提到的类和接口的存根版本:

public interface IMyInterface
{
    Task<bool> MethodA();

    Task<bool> MethodB();
}

public class Sut
{
    private readonly IMyInterface dep;

    public Sut(IMyInterface dep)
    {
        this.dep = dep;
    }

    public async Task<bool> Do()
    {
        var one = await dep.MethodA();
        var two = await dep.MethodB();

        return one || two;
    }
}

这是一个我们可以用来说明两种行为相互作用的测试:

[Test]
public async Task FixtureNoConfigureMembers()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization() { ConfigureMembers = false });

    var mock = fixture.Freeze<Mock<IMyInterface>>();

    mock.SetReturnsDefault(Task.FromResult(false));

    var sut = fixture.Create<Sut>();
    var result = await sut.Do();
    Assert.False(result);
}

要考虑的两个显着实施细节是:

  • 当您在自定义中指定时 AutoFixture 会做什么ConfigureMembers = true
  • Moq 如何确定何时使用由设置的默认值SetReturnsDefault

由于这两个项目都是开源的,因此很容易窥探到幕后。我不会在此处粘贴每个单独的类,但是如果您需要更多详细信息,可以查看Autofixture 的源代码Moq 的源代码。

Moq 的SetReturnsDefault行为相当简单:

  • 如果设置匹配,则使用该设置。默认返回值被忽略。这是重要的一点。
  • 如果没有匹配的设置,并且返回值的类型正确,则返回该默认值。

在自动夹具方面...

AutoMoqCustomization这样一来,当Mock<T>请求一个类型的样本时,AutoFixture 将首先通过调用来创建 Mock new Mock<T>()(它比这稍微微妙一些,但本质上是这个接口的情况。)

创建 mock 后,如果ConfigureMembers为 true,Autofixture 会额外枚举 mocked 类型的所有虚方法,并做相当于

mock.Setup(m => m.MethodName(It.IsAny<T>... for all arguments... ))
    .Returns(fixture.Create<TReturn>())

这会覆盖 的默认行为AutoMoqCustomization,即允许 Moq 处理默认值选择(通常通过让它创建自己的模拟)。

您可能会看到它的发展方向。因为 AutoFixture 已经为所有虚拟方法创建了设置,所以这完全覆盖了SetReturnsDefaultMoq 提供的行为。

请注意,此行为是因为 Moq 过去没有该方法SetReturnsDefault- 因此 AutoFixture 无法利用 DefaultValueProvider 来注入它创建的样本。AutoFixture 上有一个PR可以更改行为以利用DefaultValueProviderMoq 提供的属性。据推测,这将恢复SetReturnsDefault覆盖特定类型行为的能力。(虽然我还没有深入探讨过 PR)

于 2020-04-17T22:32:28.693 回答