我想创建一个工厂来为我的单元测试创建常用的模拟对象。我已经设法设置我的测试,以便我可以模拟 Linq2Sql DataContext 并返回内存中的表,而不是访问数据库。我是这样设置的:
_contactsTable = new InMemoryTable<Contact>(new List<Contact>());
_contactEmailsTable = new InMemoryTable<ContactEmail>(new List<ContactEmail>());
// repeat this for each table in the ContactsDataContext
var mockContext = new Mock<ContactsDataContext>();
mockContext.Setup(c => c.Contacts).Returns(_contactsTable);
mockContext.Setup(c => c.ContactEmails).Returns(_contactEmailsTable);
// repeat this for each table in the ContactsDataContext
如果 DataContext 包含很多表,这会变得乏味,所以我认为使用反射从 DataContext 中获取所有表的简单工厂方法可能会有所帮助:
public static DataContext GetMockContext(Type contextType)
{
var instance = new Mock<DataContext>();
var propertyInfos = contextType.GetProperties();
foreach (var table in propertyInfos)
{
//I'm only worried about ITable<> now, otherwise skip it
if ((!table.PropertyType.IsGenericType) ||
table.PropertyType.GetGenericTypeDefinition() != typeof (ITable<>)) continue;
//Determine the generic type of the ITable<>
var TableType = GetTableType(table);
//Create a List<T> of that type
var emptyList = CreateGeneric(typeof (List<>), TableType);
//Create my InMemoryTable<T> of that type
var inMemoryTable = CreateGeneric(typeof (InMemoryTable<>), TableType, emptyList);
//NOW SETUP MOCK TO RETURN THAT TABLE
//How do I call instance.Setup(i=>i.THEPROPERTYNAME).Returns(inMemoryTable) ??
}
return instance.Object;
}
到目前为止,我已经弄清楚如何创建需要为 Mock 设置的对象,但我只是不知道如何动态调用 Moq 的 Setup() 并传入属性名称。我开始研究 Invoke() Moq 的 Setup() 方法的反射,但它很快就变得非常丑陋。
有没有人有一个简单的方法来像这样动态调用Setup()和Returns()?
编辑:布莱恩的回答让我明白了。其工作原理如下:
public static DataContext GetMockContext<T>() where T: DataContext
{
Type contextType = typeof (T);
var instance = new Mock<T>();
var propertyInfos = contextType.GetProperties();
foreach (var table in propertyInfos)
{
//I'm only worried about ITable<> now, otherwise skip it
if ((!table.PropertyType.IsGenericType) ||
table.PropertyType.GetGenericTypeDefinition() != typeof(ITable<>)) continue;
//Determine the generic type of the ITable<>
var TableType = GetTableType(table);
//Create a List<T> of that type
var emptyList = CreateGeneric(typeof(List<>), TableType);
//Create my InMemoryTable<T> of that type
var inMemoryTable = CreateGeneric(typeof(InMemoryTable<>), TableType, emptyList);
//NOW SETUP MOCK TO RETURN THAT TABLE
var parameter = Expression.Parameter(contextType);
var body = Expression.PropertyOrField(parameter, table.Name);
var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
instance.Setup(lambdaExpression).Returns(inMemoryTable);
}
return instance.Object;
}
您正在寻找的是 Linq 表达式。这是实际构建属性附件表达式的示例。
使用这个类:
public class ExampleClass
{
public virtual string ExampleProperty
{
get;
set;
}
public virtual List<object> ExampleListProperty
{
get;
set;
}
}
以下测试演示了使用
Linq.Expression
类动态访问其属性。
[TestClass]
public class UnitTest1
{
[TestMethod]
public void SetupDynamicStringProperty()
{
var dynamicMock = new Mock<ExampleClass>();
//Class type
var parameter = Expression.Parameter( typeof( ExampleClass ) );
//String rep of property
var body = Expression.PropertyOrField( parameter, "ExampleProperty" );
//build the lambda for the setup method
var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );
dynamicMock.Setup( lambdaExpression ).Returns( "Works!" );
Assert.AreEqual( "Works!", dynamicMock.Object.ExampleProperty );
}
[TestMethod]
public void SetupDynamicListProperty_IntFirstInList()
{
var dynamicMock = new Mock<ExampleClass>();
var parameter = Expression.Parameter( typeof( ExampleClass ) );
var body = Expression.PropertyOrField( parameter, "ExampleListProperty" );
var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );
var listOfItems = new List<object> { 1, "two", DateTime.MinValue };
dynamicMock.Setup( lambdaExpression ).Returns( listOfItems );
Assert.AreEqual( typeof( int ), dynamicMock.Object.ExampleListProperty[0].GetType() );
Assert.AreEqual( 1, dynamicMock.Object.ExampleListProperty[0] );
Assert.AreEqual( 3, dynamicMock.Object.ExampleListProperty.Count );
}
[TestMethod]
public void SetupDynamicListProperty_StringSecondInList()
{
var dynamicMock = new Mock<ExampleClass>();
var parameter = Expression.Parameter( typeof( ExampleClass ) );
var body = Expression.PropertyOrField( parameter, "ExampleListProperty" );
var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );
var listOfItems = new List<object> { 1, "two" };
dynamicMock.Setup( lambdaExpression ).Returns( listOfItems );
Assert.AreEqual( typeof( string ), dynamicMock.Object.ExampleListProperty[1].GetType() );
Assert.AreEqual( "two", dynamicMock.Object.ExampleListProperty[1] );
Assert.AreEqual( 2, dynamicMock.Object.ExampleListProperty.Count );
}
}
编辑
您对这段代码采取的措施太过分了。此代码使用您想要的 lambda 签名创建一个方法,然后执行它 (.Invoke)。然后,您尝试将对象的结果(因此出现编译错误)传递到 Moq 的设置中。一旦您告诉 Moq 如何执行操作(因此是 lambda),Moq 就会为您执行方法并进行连接。如果您使用我提供的 lambda 表达式创建,它将构建您需要的内容。
var funcType = typeof (Func<>).MakeGenericType(new Type[] {TableType, typeof(object)});
var lambdaMethod = typeof (Expression).GetMethod("Lambda");
var lambdaGenericMethod = lambdaMethod.MakeGenericMethod(funcType);
var lambdaExpression = lambdaGenericMethod.Invoke(body, parameter);
//var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>(body, parameter); // FOR REFERENCE FROM BRIAN'S CODE
instance.Setup(lambdaExpression).Returns(inMemoryTable);
改为这样做
var parameter = Expression.Parameter( TableType );
var body = Expression.PropertyOrField( parameter, "PutYourPropertyHere" );
var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );
instance.Setup(lambdaExpression).Returns(inMemoryTable);
编辑
尝试更正 GetMockContext。请注意一些更改(我标记了每一行)。我认为这更接近。我想知道,InMemoryTable继承自DataContext吗?如果不是,方法签名将不正确。
public static object GetMockContext<T>() where T: DataContext
{
Type contextType = typeof (T);
var instance = new Mock<T>(); //Updated this line
var propertyInfos = contextType.GetProperties();
foreach (var table in propertyInfos)
{
//I'm only worried about ITable<> now, otherwise skip it
if ((!table.PropertyType.IsGenericType) ||
table.PropertyType.GetGenericTypeDefinition() != typeof(ITable<>)) continue;
//Determine the generic type of the ITable<>
var TableType = GetTableType(table);
//Create a List<T> of that type
var emptyList = CreateGeneric(typeof(List<>), TableType);
//Create my InMemoryTable<T> of that type
var inMemoryTable = CreateGeneric(typeof(InMemoryTable<>), TableType, emptyList);
//NOW SETUP MOCK TO RETURN THAT TABLE
var parameter = Expression.Parameter(contextType);
var body = Expression.PropertyOrField(parameter, table.Name);
var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
instance.Setup(lambdaExpression).Returns(inMemoryTable);
}
return instance.Object; //had to change the method signature because the inMemoryTable is not of type DataContext. Unless InMemoryTable inherits from DataContext?
}
我希望这有帮助!
这与我所需要的非常相似,只是我想基于父类型的实例或实例以及属性值的动态类型来最小起订量接口或类的所有属性。这似乎运作良好:
public static void SetupGetProperties<TMockType, TMockTypeParent>(this Mock<TMockType> mock, TMockTypeParent parent) where TMockType : class, TMockTypeParent
{
var parentType = typeof(TMockTypeParent);
var properties = parentType.GetProperties().ToList();
if (parentType.IsInterface)
{
properties.AddRange(parentType.GetInterfaces().SelectMany(i => i.GetProperties()));
}
foreach (var property in properties.Where(p => p.CanRead))
{
var parameter = Expression.Parameter(property.DeclaringType);
var body = Expression.PropertyOrField(parameter, property.Name);
var value = property.GetValue(parent);
var callSetupGet = typeof(Extension).GetMethod(nameof(CallSetupGet),BindingFlags.NonPublic | BindingFlags.Static) ?? throw new Exception("Unable to create method " + nameof(CallSetupGet));
var callSetupGetGeneric = callSetupGet.MakeGenericMethod(typeof(TMockType), property.PropertyType);
callSetupGetGeneric.Invoke(null, new [] { mock, parameter, body, value });
}
}
private static void CallSetupGet<TMockType, TParam>(Mock<TMockType> mock, ParameterExpression parameter, MemberExpression body, object value) where TMockType : class
{
var lambdaExpression = Expression.Lambda<Func<TMockType, TParam>>(body, parameter);
mock.SetupGet(lambdaExpression).Returns((TParam)value);
}
对于匹配类型或继承类型,可以简单地以这种方式调用:
var mock = new Mock<ISomeInterface>();
if (mockImplementation != null)
{
mock.SetupGetProperties(mockImplementation);
}