Как зафиксировать настройку значения свойства с помощью Mock (Moq)

#c# #mocking #moq

#c# #издевательство #moq

Вопрос:

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

Я привел короткий пример:

Макет интерфейса:

 public interface ISomeInterface
{
    string SomeProperty { get; set; }
}
  

SUT:

 public class SomeSystemUnderTest
{
    public void AssignSomeValueToThis(ISomeInterface obj)
    {
        obj.SomeProperty = Guid.NewGuid().ToString();
    }
}
  

Тест:

 [TestClass]
public class SomeTests
{
    [TestMethod]
    public void TestSomeSystem()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();

        someInterfaceMock.SetupSet(m => m.SomeProperty = It.IsAny<string>()).Verifiable();

        // Act
        var sut = new SomeSystemUnderTest();
        sut.AssignSomeValueToThis(someInterfaceMock.Object);

        // Assert
        // HERE I WOULD LIKE TO READ WHAT VALUE WAS ASSIGNED
        string myVal = someInterfaceMock.Object.SomeProperty;
    }
}
  

Переменная «myVal» остается нулевой, и, изучив макет, мы можем увидеть, что свойство по-прежнему равно null. Я действительно не ожидал, что это будет иметь какое-то значение, просто пытаюсь.

Я пробовал с настройкой, при обратном вызове я получаю ошибки компиляции.

В реальном проекте SUT заключается в преобразовании издевательского свойства объекта во что-то, зависящее от другого свойства объекта. Чтобы знать, выполняет ли объект свою работу, мне нужно иметь возможность прочитать свойство. Обратите внимание, я не могу переделать макетные интерфейсы, они являются сторонними.

Я пытался использовать VerifySet, но, похоже, он принимает только жестко заданное значение.

Спасибо, Мишель

Ответ №1:

Существует разница между get и set , и mock фактически не имеет никакого внутреннего состояния, а только настройки, которым он пытается соответствовать и вести себя должным образом. Вы могли бы имитировать реальную get и set функциональность с помощью обратного вызова. Что-то вроде этого:

 //Arrange
string someProperty = null;
var mock = new Mock<ISomeInterface>();

mock.SetupSet(m => m.SomeProperty = It.IsAny<string>())
    .Callback<string>(p => someProperty = p)
    .Verifiable();

// use func instead of value to defer the resulution to the invocation moment
mock.SetupGet(m => m.SomeProperty).Returns(() => someProperty);

//Act
mock.Object.SomeProperty = "test";

//Assert
Assert.AreEqual("test", mock.Object.SomeProperty);
  

Другая возможность заключается в использовании Capture самого по себе, в котором оно фактически существует moq

 //Arrange
List<string> someProperty = new List<string>();
var mock = new Mock<ISomeInterface>();

mock.SetupSet(m => m.SomeProperty = Capture.In(someProperty))
    .Verifiable();

mock.SetupGet(m => m.SomeProperty).Returns(() => someProperty.Last());

//Act
mock.Object.SomeProperty = "test";

//Assert
Assert.AreEqual("test", mock.Object.SomeProperty);
  

Ответ №2:

Общий Ответ

У Moq на самом деле есть специальные методы для этого:

  • Для настройки отдельного свойства: mock.SetupProperty(m => m.SomeProperty) , при необходимости передавая значение по умолчанию
  • Для настройки всех свойств: mock.SetupAllProperties()

Конкретные случаи

Однако есть конкретные случаи, когда этого недостаточно, например, если мы хотим настроить свойство и присвоить ему значение по умолчанию, не устанавливая значение свойства с помощью установщиков свойств.

(Один из примеров — это когда нам нужно свойство с установленным значением для использования в конструкторе, и в этом случае мы не можем установить свойство напрямую, поскольку для этого потребуется вызов .Object , который неявно вызывает конструктор).

В этой ситуации следует использовать ответ @Johnny.

Вот общая версия метода расширения его ответа:

 public static void SetupAutoProperty<TObject, TProperty>(this Mock<TObject> mock,
       Expression<Func<TObject, TProperty>> memberAccessExpr, 
       TProperty initialValue) where TObject : class
{
     var propStates = new List<TProperty>();            
     Expression<Action> captureExpression = () => Capture.In(propStates);

     var finalExpression = Expression.Lambda<Action<TObject>>(
                 Expression.Assign(memberAccessExpr.Body, captureExpression.Body),
                                        memberAccessExpr.Parameters);

      mock.SetupSet(finalExpression.Compile());
      mock.SetupGet(memberAccessExpr).Returns(() => 
                 propStates.Any() ? propStates.Last() : initialValue);
}
  

И назовите это так, как в someInterfaceMock.SetupAutoProperty(m => m.SomeProperty, someInitialValue)