我使用ControllerActionInvoker
从单元测试中调用控制器操作
var controllerInvoker = new ControllerActionInvoker();
var result = controllerInvoker.InvokeAction(
testController.ControllerContext, "Default" );
如何使用它来调用具有参数的动作?
[AcceptVerbs( HttpVerbs.Post )]
[ActionException( SomeAttribute )]
public SomeResult AddMethod( long[] Ids )
{
//some code
}
答案 0 :(得分:1)
从文档中看,您希望使用InvokeActionMethod方法,该方法允许您将IDictionary中的参数作为第三个参数传递。
ControllerContext实际上带有控制器将用于绑定的附加数据(过滤器,模型绑定器,路由数据)。您的参数需要通过ControllerContext传递。
答案 1 :(得分:0)
您不应在单元测试中使用ControllerActionInvoker。你到底想要完成什么?
如果您正在尝试测试行为的行为,请直接调用它们(它们只是常规方法)。如果您正在尝试测试过滤器的行为,请为过滤器创建模拟上下文并调用其OnXxx()方法。
答案 2 :(得分:0)
我之所以使用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();
}
}