我使用 ControllerActionInvoker 是因为我想围绕我的控制器编写规范测试,而不是低级单元测试。我发现我对 ControllerActionInvoker 的实现必须根据我正在测试的内容进行改进,但以下内容对我有用。
class ControllerSpecActionInvoker<TResult> : ControllerActionInvoker where
TResult : ActionResult
{
private readonly Expression body;
public ControllerSpecActionInvoker(Expression body)
{
this.body = body;
}
public TResult Result { get; private set; }
protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
=> Result = actionResult as TResult;
protected override IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (body is MethodCallExpression methodCall)
{
return methodCall.Method.GetParameters()
.Zip(methodCall.Arguments.Select(GetArgumentAsConstant), (param, arg) => new { param.Name, Value = ChangeType(arg.Value, param.ParameterType) })
.ToDictionary(item => item.Name, item => item.Value);
}
return base.GetParameterValues(controllerContext, actionDescriptor);
}
private ConstantExpression GetArgumentAsConstant(Expression exp)
{
switch (exp)
{
case ConstantExpression constExp:
return constExp;
case UnaryExpression uranExp:
return GetArgumentAsConstant(uranExp.Operand);
}
throw new NotSupportedException($"Cannot handle expression of type '{exp.GetType()}'");
}
private static object ChangeType(object value, Type conversion)
{
var t = conversion;
if (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(Nullable<>)) return Convert.ChangeType(value, t);
if (value == null) return null;
t = Nullable.GetUnderlyingType(t);
return Convert.ChangeType(value, t);
}
}
出于我的目的,这用于规范夹具基类和自动模拟依赖项,但您使用它的本质是这样的:
var actionInvoker = new ControllerSpecActionInvoker<ActionResult>(Expression<Func<ActionResult|JsonResult|Etc>>);
actionInvoker.InvokeAction(<controller context>, <name of the action>);
Result = actionInvoker.Result;
所以它可能看起来像这样,不是测试。大多数杂乱无章的东西可以隐藏在基类中:
class MyController : Controller
{
JsonResult MyAction(int i) { return Json(new {}); }
}
class MyControllerFixture
{
[Test]
public void ReturnsData()
{
var controller = new MyController();
var controllerContext = new ControllerContext
{
RouteData = new RouteData(),
HttpContext = httpContextBase,
};
controllerContext.Controller = controller;
controller.ControllerContext = controllerContext;
Action<JsonResult> act = controller.MyAction(1);
var actionInvoker = new ControllerSpecActionInvoker<JsonResult>(act.Body);
actionInvoiker.Result.Should().NotBeNull();
}
}