如何使用 Mock (Moq) 捕获 属性 值的设置
How to capture the setting of a property value with Mock (Moq)
在我的测试项目中,我想捕获 SUT 设置的 属性 到模拟对象。我已经尝试了很多东西,但 none 似乎让我能够捕捉到它。
我设置了一个简短的例子:
模拟界面:
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" 变量保持为空,通过检查模拟,我们可以看到 属性 仍然为空。我真的没想到它有什么价值,只是尝试。
我尝试使用安装程序,使用回调,但出现编译错误。
在实际项目中,SUT 是将模拟对象 属性 转换为依赖于另一个对象 属性 的对象。要知道该对象是否正在执行其工作,我需要能够读取 属性。请注意,我无法重新设计模拟接口,它们是第 3 方。
我尝试使用 VerifySet,但它似乎只采用硬编码值。
谢谢,
米歇尔
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);
更新
事实证明,Moq 实际上有特殊的方法:
- 要设置个人 属性:
mock.SetupProperty(m => m.SomeProperty)
,可选择传递默认值
- 设置所有属性:
mock.SetupAllProperties()
我还是把原来的答案留在这里,以防有人想进一步定制它。
原答案
感谢@Johnny 的精彩回答。
我已经在通用扩展方法中将其作为答案,您可以通过以下扩展方法像 someInterfaceMock.SetupAutoProperty(m => m.SomeProperty)
中那样使用它:
public static void SetupAutoProperty<TObject, TProperty>(this Mock<TObject> mock,
Expression<Func<TObject, TProperty>> memberAccessExpr) 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.LastOrDefault());
}
在我的测试项目中,我想捕获 SUT 设置的 属性 到模拟对象。我已经尝试了很多东西,但 none 似乎让我能够捕捉到它。
我设置了一个简短的例子:
模拟界面:
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" 变量保持为空,通过检查模拟,我们可以看到 属性 仍然为空。我真的没想到它有什么价值,只是尝试。
我尝试使用安装程序,使用回调,但出现编译错误。
在实际项目中,SUT 是将模拟对象 属性 转换为依赖于另一个对象 属性 的对象。要知道该对象是否正在执行其工作,我需要能够读取 属性。请注意,我无法重新设计模拟接口,它们是第 3 方。
我尝试使用 VerifySet,但它似乎只采用硬编码值。
谢谢, 米歇尔
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);
更新
事实证明,Moq 实际上有特殊的方法:
- 要设置个人 属性:
mock.SetupProperty(m => m.SomeProperty)
,可选择传递默认值 - 设置所有属性:
mock.SetupAllProperties()
我还是把原来的答案留在这里,以防有人想进一步定制它。
原答案
感谢@Johnny 的精彩回答。
我已经在通用扩展方法中将其作为答案,您可以通过以下扩展方法像 someInterfaceMock.SetupAutoProperty(m => m.SomeProperty)
中那样使用它:
public static void SetupAutoProperty<TObject, TProperty>(this Mock<TObject> mock,
Expression<Func<TObject, TProperty>> memberAccessExpr) 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.LastOrDefault());
}