在没有 Mock.Setup() 的情况下调用 Mock.Returns()
Call Mock.Returns() without Mock.Setup()
使用 Moq 时,您经常需要将 mock 配置为 return 特定值。要指定您想要 return 的内容,您必须先完成使用以下结构定义 Setup() 的例程,然后才能定义您的方法应该 return:
var o = new ObjectToReturn();
myMock.Setup(m => m.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>()))
.Returns(o);
相反,我想做 "For methods with this name, return this value",比如:
myMock.Setup(m => m.GetType().GetMethod("MyMethod")).Returns(o);
或
myMock.Setup("MyMethod").Return(o);
当我根本不关心类型、值或参数数量时,是否有任何其他方法可以跳过所有方法参数的冗长枚举?
我知道有一个方法 SetReturnsDefault() 但我不想为模拟的所有方法设置默认值。
这是可能的。尝试:
public interface IService
{
int MyMethod1(int a, object b);
int MyMethod2(int a);
int MyMethod2(object b);
}
public static class MyMockExtensions
{
public static ISetup<T, TResult> Setup<T, TResult>(this Mock<T> mock,
string methodName) where T : class
{
var methods = typeof(T).GetMethods()
.Where(
mi => mi.Name == methodName
&& mi.ReturnType == typeof(TResult))
.ToArray();
if (methods.Length == 0)
{
throw new MissingMethodException("No method found.");
}
if (methods.Length > 1)
{
throw new AmbiguousMatchException("Ambiguous methods found.");
}
var method = methods[0];
// Figure out parameters.
var parameters = method.GetParameters()
// It.IsAny<pi.ParameterType>()
.Select(pi => Expression.Call(
typeof(It), nameof(It.IsAny), new[] { pi.ParameterType }));
// arg0 => arg0.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>())
var arg0 = Expression.Parameter(typeof(T), "arg0");
var setupExpression = Expression.Lambda<Func<T, TResult>>(
Expression.Call(arg0, method, parameters), arg0);
return mock.Setup(setupExpression);
}
}
用法:
var mock = new Mock<IService>();
mock.Setup<IService, int>("MyMethod1").Returns(123);
// method1Result = 123
var method1Result = mock.Object.MyMethod1(1, 2);
// This throws AmbiguousMatchException exception as there are two MyMethod3
// both returning an integer.
mock.Setup<IService, int>("MyMethod2").Returns(234);
挑战在于处理由定义的不同类型的方法
你的模拟类型:
- 正常方法
- 通用方法
- 方法重载
默认值提供程序可能是一个选项。这与 SetReturnsDefault
不同,您有更多的控制权。
A real quick MVP 默认值提供程序如下:
public class SelectiveDefaultValueProvider : DefaultValueProvider
{
private readonly string _methodName;
private readonly object _returns;
public SelectiveDefaultValueProvider(string methodName, object returns)
{
_methodName = methodName;
_returns = returns;
}
protected override object GetDefaultValue(Type type, Mock mock)
{
var lastInvocation = mock.Invocations.Last();
var methodInfo = lastInvocation.Method;
var args = lastInvocation.Arguments;
if (methodInfo.Name.Equals(_methodName))
{
return _returns;
}
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
...允许您在值被 returned 之前注入一些决策。在这种情况下,我正在检查最后一个调用方法名称,如果匹配,我将 return 指定的对象 return。我没有使用 args 变量,但我已经包含它以表明您不仅获得了上次调用的 MethodInfo
,而且还获得了提供的参数。足以做出明智的决定。
采用以下带有一些重载方法和相同 return 类型的接口:
public class ObjectToReturn
{
public Guid Id { get; set; }
}
public interface IFoo
{
ObjectToReturn MyMethod(int parameter1);
ObjectToReturn MyMethod(string parameter2);
ObjectToReturn MyMethod(int parameter1, int parameter2);
ObjectToReturn AnotherMethod();
int AValueTypeMethod();
}
测试设置看起来像
[Test]
public void DefaultValueProvider_ForOverloadedMethod_AllOverloadsReturnSameExpectedResult()
{
var objectToReturn = new ObjectToReturn { Id = Guid.NewGuid() };
var mock = new Mock<IFoo> { DefaultValueProvider = new SelectiveDefaultValueProvider(nameof(IFoo.MyMethod), objectToReturn) };
var mocked = mock.Object;
var result1 = mocked.MyMethod(1);
var result2 = mocked.MyMethod(1, 2);
var result3 = mocked.MyMethod("asdf");
var result4 = mocked.AnotherMethod();
var result5 = mocked.AValueTypeMethod();
Assert.Multiple(() =>
{
Assert.That(result1, Is.SameAs(objectToReturn));
Assert.That(result2, Is.SameAs(objectToReturn));
Assert.That(result3, Is.SameAs(objectToReturn));
Assert.That(result4, Is.Null);
Assert.That(result5, Is.TypeOf<int>());
});
}
正如上面提到的 MVP,您可以轻松扩展默认值提供程序实现以获取 methods/return 值的列表,收紧从字符串方法名称到 MethodInfo
结果的接口(typeof(IFoo).GetMethods().Where(x => x.Name.StartsWith("MyMethod") && x.ReturnType == typeof(ObjectToReturn))
) 等等。
这种方法的好处是您不必担心构建 Setup
表达式和由此带来的麻烦,如果您确实为成员指定了设置,它将使用该设置;默认值提供程序仅用于没有指定设置的成员。
使用 Moq 时,您经常需要将 mock 配置为 return 特定值。要指定您想要 return 的内容,您必须先完成使用以下结构定义 Setup() 的例程,然后才能定义您的方法应该 return:
var o = new ObjectToReturn();
myMock.Setup(m => m.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>()))
.Returns(o);
相反,我想做 "For methods with this name, return this value",比如:
myMock.Setup(m => m.GetType().GetMethod("MyMethod")).Returns(o);
或
myMock.Setup("MyMethod").Return(o);
当我根本不关心类型、值或参数数量时,是否有任何其他方法可以跳过所有方法参数的冗长枚举?
我知道有一个方法 SetReturnsDefault() 但我不想为模拟的所有方法设置默认值。
这是可能的。尝试:
public interface IService
{
int MyMethod1(int a, object b);
int MyMethod2(int a);
int MyMethod2(object b);
}
public static class MyMockExtensions
{
public static ISetup<T, TResult> Setup<T, TResult>(this Mock<T> mock,
string methodName) where T : class
{
var methods = typeof(T).GetMethods()
.Where(
mi => mi.Name == methodName
&& mi.ReturnType == typeof(TResult))
.ToArray();
if (methods.Length == 0)
{
throw new MissingMethodException("No method found.");
}
if (methods.Length > 1)
{
throw new AmbiguousMatchException("Ambiguous methods found.");
}
var method = methods[0];
// Figure out parameters.
var parameters = method.GetParameters()
// It.IsAny<pi.ParameterType>()
.Select(pi => Expression.Call(
typeof(It), nameof(It.IsAny), new[] { pi.ParameterType }));
// arg0 => arg0.MyMethod(It.IsAny<T1>()...It.IsAny<Tn>())
var arg0 = Expression.Parameter(typeof(T), "arg0");
var setupExpression = Expression.Lambda<Func<T, TResult>>(
Expression.Call(arg0, method, parameters), arg0);
return mock.Setup(setupExpression);
}
}
用法:
var mock = new Mock<IService>();
mock.Setup<IService, int>("MyMethod1").Returns(123);
// method1Result = 123
var method1Result = mock.Object.MyMethod1(1, 2);
// This throws AmbiguousMatchException exception as there are two MyMethod3
// both returning an integer.
mock.Setup<IService, int>("MyMethod2").Returns(234);
挑战在于处理由定义的不同类型的方法 你的模拟类型:
- 正常方法
- 通用方法
- 方法重载
默认值提供程序可能是一个选项。这与 SetReturnsDefault
不同,您有更多的控制权。
A real quick MVP 默认值提供程序如下:
public class SelectiveDefaultValueProvider : DefaultValueProvider
{
private readonly string _methodName;
private readonly object _returns;
public SelectiveDefaultValueProvider(string methodName, object returns)
{
_methodName = methodName;
_returns = returns;
}
protected override object GetDefaultValue(Type type, Mock mock)
{
var lastInvocation = mock.Invocations.Last();
var methodInfo = lastInvocation.Method;
var args = lastInvocation.Arguments;
if (methodInfo.Name.Equals(_methodName))
{
return _returns;
}
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
...允许您在值被 returned 之前注入一些决策。在这种情况下,我正在检查最后一个调用方法名称,如果匹配,我将 return 指定的对象 return。我没有使用 args 变量,但我已经包含它以表明您不仅获得了上次调用的 MethodInfo
,而且还获得了提供的参数。足以做出明智的决定。
采用以下带有一些重载方法和相同 return 类型的接口:
public class ObjectToReturn
{
public Guid Id { get; set; }
}
public interface IFoo
{
ObjectToReturn MyMethod(int parameter1);
ObjectToReturn MyMethod(string parameter2);
ObjectToReturn MyMethod(int parameter1, int parameter2);
ObjectToReturn AnotherMethod();
int AValueTypeMethod();
}
测试设置看起来像
[Test]
public void DefaultValueProvider_ForOverloadedMethod_AllOverloadsReturnSameExpectedResult()
{
var objectToReturn = new ObjectToReturn { Id = Guid.NewGuid() };
var mock = new Mock<IFoo> { DefaultValueProvider = new SelectiveDefaultValueProvider(nameof(IFoo.MyMethod), objectToReturn) };
var mocked = mock.Object;
var result1 = mocked.MyMethod(1);
var result2 = mocked.MyMethod(1, 2);
var result3 = mocked.MyMethod("asdf");
var result4 = mocked.AnotherMethod();
var result5 = mocked.AValueTypeMethod();
Assert.Multiple(() =>
{
Assert.That(result1, Is.SameAs(objectToReturn));
Assert.That(result2, Is.SameAs(objectToReturn));
Assert.That(result3, Is.SameAs(objectToReturn));
Assert.That(result4, Is.Null);
Assert.That(result5, Is.TypeOf<int>());
});
}
正如上面提到的 MVP,您可以轻松扩展默认值提供程序实现以获取 methods/return 值的列表,收紧从字符串方法名称到 MethodInfo
结果的接口(typeof(IFoo).GetMethods().Where(x => x.Name.StartsWith("MyMethod") && x.ReturnType == typeof(ObjectToReturn))
) 等等。
这种方法的好处是您不必担心构建 Setup
表达式和由此带来的麻烦,如果您确实为成员指定了设置,它将使用该设置;默认值提供程序仅用于没有指定设置的成员。