将对象的 属性 模拟为来自对象本身的值 return

Mock a property of an object to return a value that is coming from the object itself

我想用一个对象创建一个模拟 return 一个 属性 来自同一模拟实例的另一个 属性 的值,但我没有找到如何使用最小起订量进行设置。

我为 Setup/SetupGet 找到的所有签名都只允许 return 常量值,而不是来自同一对象的值。

示例:

void Main()
{
    var obj = GetMock().Object;

    obj.PropA = "test";

    Console.WriteLine(obj.Id); // Should return -354185609, as it's the result of "test".GetHashCode()

    obj.PropA = "test 2";

    Console.WriteLine(obj.Id); // Should return -1555281747, as it's the result of "test 2".GetHashCode()

}

public interface IMyObject
{
    int Id { get;set;}

    string PropA {get; set;}
}

public Mock<IMyObject> GetMock()
{
    var objectMock = new Mock<IMyObject>();

    objectMock.SetupAllProperties();

    objectMock
        .SetupGet((IMyObject s) => s.Id)
        //Here I would like to write something like : 
        .Returns(s => (a.PropA?.GetHashCode()).GetValueOrDefault()));
        // To have the Getter of Id initialized with the current value of PropA.GetHashCode()

    return objectMock;
}

感谢您的建议

您可以使用 Callback 来存储来自 set 的值,然后在 Returns 中重复使用它。像这样:

string propA = null;

var mock = new Mock<IMyObject>();
mock.SetupSet(m => m.PropA = It.IsAny<string>())
    .Callback<string>(s => propA = s);
mock.Setup(m => m.Id)
    .Returns(() => (propA?.GetHashCode()).GetValueOrDefault());

mock.Object.PropA = "test";

Assert.Equal("test".GetHashCode(), mock.Object.Id);

mock.Object.PropA = "test 2";

Assert.Equal("test 2".GetHashCode(), mock.Object.Id);

另一种选择是实际实施并使用 CallBase 功能。 (类似于

public abstract class MyObjectDummy : IMyObject
{
    public virtual int Id { get; set; }  
    public virtual string PropA { get; set; }
}

var mock = new Mock<MyObjectDummy>();
mock.SetupSet(m => m.PropA = It.IsAny<string>()).CallBase();
mock.SetupGet(m => m.PropA).CallBase();
mock.Setup(m => m.Id).Returns(() => (mock.Object.PropA?.GetHashCode()).GetValueOrDefault());

mock.Object.PropA = "test";
Assert.Equal("test".GetHashCode(), mock.Object.Id);

有时,最小起订量设置变得非常难以阅读,通常到了其他人很难说出正在测试什么的地步。在那些场景中,我们通常可以 "mock" 使用测试替身 - 即实现接口的 class。

举个例子。我不能肯定地说这是否能达到您要对模拟进行的操作,但这确实是重点。阅读该设置并说出它的作用并不容易。另一方面,很容易看出这是做什么的:

public class ObjectWhereIdReturnsPropAHashCode : IMyObject
{
    public int Id
    {
        get => PropA?.GetHashCode() ?? 0;
        set => Id = value;
    }

    public string PropA { get; set; }
} 

...然后创建一个做同样事情的 Mock<IObject> 要容易得多。

当您创建测试替身的实例时,class 的名称可以清楚地表明该测试替身在特定测试中的作用。 (公平地说,您也可以使用 Moq 来创建一个命名良好的函数来创建和 returns 模拟。)

对于必须创建新测试替身或修改现有测试替身的下一位开发人员来说,这也是一种更容易遵循的模式。

我不是建议我们不应该使用 Moq - 我一直在使用它 - 只是当它开始变得不必要地复杂时我们应该考虑替代方案。