Moq + Autofixture:对依赖属性使用 Setup 清除整个模拟对象

问题描述

我使用的是 Moq 和 AutoFixture。

给定以下接口:

public interface Int1
{
    Int2 Int2 { get; }
}

public interface Int2
{
    string Prop1 { get; }
    string Prop2 { get; }
}

我正在执行以下测试:

using AutoFixture;
using AutoFixture.automoq;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
public class TestClass
{
    [TestMethod]
    public void Test1()
    {
        var f = new Fixture().Customize(new automoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        obj.Object.Int2.Prop1.Should().NotBeNullOrEmpty();
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }

    [TestMethod]
    public void Test2()
    {
        var f = new Fixture().Customize(new automoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        obj.Setup(q => q.Int2.Prop1).Returns("test");

        obj.Object.Int2.Prop1.Should().Be("test");
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }
}

一个测试通过而第二个测试失败:Expected obj.Object.Int2.Prop2 not to be <null> or empty,but found <null>。似乎在 Int2一个依赖属性上使用 Setup 时,它会清除整个 Int2 对象(将所有属性设置为认值)。这是为什么?如何避免?

创建后的

obj.Object 如下所示:

Before Setup

但是在执行 Setup 之后它看起来像这样(Prop2null):

After Setup

有趣的是,当我在创建后访问 Int2 属性时,它工作正常。所以这个测试通过了(变量 int2 没有在任何地方使用):

    [TestMethod]
    public void Test2()
    {
        var f = new Fixture().Customize(new automoqCustomization { ConfigureMembers = true });

        var obj = f.Create<Mock<Int1>>();

        var int2 = obj.Object.Int2;

        obj.Setup(q => q.Int2.Prop1).Returns("test");

        obj.Object.Int2.Prop1.Should().Be("test");
        obj.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
    }

有什么想法吗?

这也是一个 .csproj 文件供参考:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <packagereference Include="AutoFixture" Version="4.15.0" />
        <packagereference Include="AutoFixture.automoq" Version="4.15.0" />
        <packagereference Include="FluentAssertions" Version="5.10.3" />
        <packagereference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
        <packagereference Include="Moq" Version="4.16.1" />
        <packagereference Include="MSTest.TestAdapter" Version="2.1.2" />
        <packagereference Include="MSTest.TestFramework" Version="2.1.2" />
    </ItemGroup>
</Project>

解决方法

简而言之,在为 Int2 属性设置返回值后,您最终会得到一个不同的 Prop1 属性模拟实例。为了满足您的请求,Moq 将生成一个新的模拟,它将返回 Prop1 的预期值。

您可以将其视为等效于以下测试:

[Fact]
public void Test3()
{
    var obj2 = new Mock<IInterfaceB>();
    obj2.Setup(x => x.Property1).Returns(Guid.NewGuid().ToString());
    obj2.Setup(x => x.Property2).Returns(Guid.NewGuid().ToString());

    var obj = new Mock<IInterfaceA>();
    obj.Setup(x => x.PropertyB).Returns(obj2.Object);

    obj.Setup(q => q.PropertyB.Property1).Returns("test");

    Assert.Equal("test",obj.Object.PropertyB.Property1);
    Assert.NotEmpty(obj.Object.PropertyB.Property2);
}

如果您想保留您的初始模拟实例并只更改 Prop1,那么您可以使用 Mock.Get<T>(T)

[Fact]
public void Test4()
{
    var f = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });

    var obj = f.Create<Mock<IInterfaceA>>();

    var obj2 = Mock.Get(obj.Object.PropertyB);
    obj2.Setup(q => q.Property1).Returns("test");

    Assert.Equal("test",obj.Object.PropertyB.Property1);
    Assert.NotEmpty(obj.Object.PropertyB.Property2);
}

但我会推荐使用 AutoFixture 的冻结功能

[TestMethod]
public void Test5()
{
    var fixture = new Fixture()
        .Customize(new AutoMoqCustomization { ConfigureMembers = true });

    var int2Mock = fixture.Freeze<Mock<Int2>>();
    var int1Mock = fixture.Create<Mock<Int1>>();

    int2Mock.Setup(q => q.Prop1).Returns("test");

    int1Mock.Object.Int2.Prop1.Should().Be("test");
    int1Mock.Object.Int2.Prop2.Should().NotBeNullOrEmpty();
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...