#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)