Смоделируйте свойство объекта для возврата значения, которое исходит от самого объекта - PullRequest
1 голос
/ 02 мая 2019

Я хочу создать макет с объектом, возвращающим для одного свойства значение, полученное из другого свойства того же экземпляра макета, но я не могу найти способ его настройки с помощью Moq.

Все подписиЯ считаю, что для Setup / SetupGet разрешено возвращать только постоянные значения, а не значения, поступающие из одного и того же объекта.

Пример:

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;
}

Спасибо за ваши предложения

Ответы [ 2 ]

1 голос
/ 02 мая 2019

Иногда настройки Moq очень трудно читать, часто до такой степени, что кому-то еще трудно сказать, что тестируется. В этих сценариях мы часто можем «насмехаться», используя двойной тест, то есть класс, который реализует интерфейс.

Вот пример. Я не могу сказать наверняка, если это делает то, что вы пытаетесь сделать с вашей насмешкой, но это действительно главное. Нелегко прочитать эту настройку и рассказать, что она делает. С другой стороны, действительно легко сказать, что это делает:

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

    public string PropA { get; set; }
} 

... и было гораздо проще создать Mock<IObject>, который делает то же самое.

Когда вы создаете экземпляр двойного теста, имя класса может четко указывать, что этот двойной тест делает для целей данного конкретного теста. (Чтобы быть справедливым, вы также можете сделать это с Moq, сделав хорошо названную функцию, которая создает и возвращает макет.)

Это также более простой шаблон для следующего разработчика, который должен либо создать новый двойной тест, либо изменить существующий.

Я не предлагаю, чтобы мы не использовали Moq - я использую его все время - просто чтобы мы смотрели на альтернативы всякий раз, когда он начинает становиться излишне сложным.

1 голос
/ 02 мая 2019

Вы можете использовать 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);
...