如何在 ASP.NET Web Api 中对 Action Filter 进行单元测试?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11848137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 16:33:31  来源:igfitidea点击:

How can you unit test an Action Filter in ASP.NET Web Api?

.netunit-testingasp.net-web-apiaction-filter

提问by Aaron Fischer

I was looking to add an Action Filter to my service to handle adding link data to the response message. I have found that I need to mock HttpActionExecutedContext but it's a difficult class to mock, how are you dealing with Action Filter testing?

我希望在我的服务中添加一个操作过滤器来处理向响应消息添加链接数据。我发现我需要模拟 HttpActionExecutedContext 但它是一个很难模拟的类,你如何处理 Action Filter 测试?

回答by tugberk

You can create a fake for HttpActionExecutedContextas below:

您可以创建一个假货HttpActionExecutedContext如下:

public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
{
    HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
    HttpActionDescriptor descriptor = actionDescriptor ?? new Mock<HttpActionDescriptor>() { CallBase = true }.Object;
    return new HttpActionContext(context, descriptor);
}

public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response)
{
    HttpActionContext actionContext = CreateActionContext();
    actionContext.ControllerContext.Request = request;
    HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response };
    return actionExecutedContext;
}

I just copied and pasted that code from the ASP.NET Web API source code: ContextUtilclass. Here is a few examples on how they tested some built in filters:

我刚刚从 ASP.NET Web API 源代码中复制并粘贴了该代码:ContextUtil类。以下是一些关于他们如何测试一些内置过滤器的示例:

ActionFilterAttributeTestis the test class for ActionFilterAttributewhich is an abstract class but you will get the idea.

ActionFilterAttributeTest是测试类,ActionFilterAttribute它是一个抽象类,但你会明白的。

回答by Sam Shiles

Just new one up.

刚上新。

private HttpActionContext CreateExecutingContext()
{
    return new HttpActionContext { ControllerContext = new HttpControllerContext {   Request = new HttpRequestMessage() } };
}

private HttpActionExecutedContext CreateExecutedContextWithStatusCode(HttpStatusCode statusCode)
{
    return new HttpActionExecutedContext
    {
        ActionContext = new HttpActionContext
        {
            ControllerContext = new HttpControllerContext
            {
                Request = new HttpRequestMessage()
            }
        },
        Response = new HttpResponseMessage
        {
            StatusCode = statusCode,
            Content = new StringContent("blah")
        }
    };
}

回答by will webster

I had the same problem when trying to test a custom unhandled exception filter I had built.

在尝试测试我构建的自定义未处理异常过滤器时,我遇到了同样的问题。

This did the trick. Lots of newing up and a very long line of code.

这成功了。很多新的东西和很长的代码行。

var httpActionExecutedContext = new HttpActionExecutedContext(
    new HttpActionContext(
        new HttpControllerContext(
            new HttpConfiguration(),
            Substitute.For<IHttpRouteData>(),
            new HttpRequestMessage()),
    Substitute.For<HttpActionDescriptor>()),
    null);

NSubstiute was used, but any mocking framework of your choice that handles abstract base classes would be fine.

使用了 NSubstiute,但是您选择的任何处理抽象基类的模拟框架都可以。

Hope this helps

希望这可以帮助

回答by outofcoolnames

I've been banging my head against a brick wall over this also. I tried contextUtilbut kept getting a null reference exception. I found out how to call an actionFilter in this postN.B. The actionFilter wasn't being invoked when using a Mock instance of the filter, I had to use the real object. HTH

我也一直在用头撞砖墙。我尝试了contextUtil,但一直收到空引用异常。我在这篇文章中发现了如何调用 actionFilter NB 在使用过滤器的 Mock 实例时没有调用 actionFilter,我必须使用真实对象。HTH

Specifically:

具体来说:

var httpActionContext = new HttpActionContext
{
    ControllerContext = new HttpControllerContext
    {
        Request = requestMessage
    }
};

//call filter
var filter = new FooFilter();
filter.OnActionExecuting(httpActionContext);

回答by 3DPrintScanner

Referencing https://stackoverflow.com/a/44447349/5547177

参考https://stackoverflow.com/a/44447349/5547177

You can create an HTTPActionContext yourself with the following:

您可以使用以下内容自己创建 HTTPActionContext:

 _ctx = new HttpActionContext
        {
            ControllerContext = new HttpControllerContext()
            {
                Request = new HttpRequestMessage()

            }
        };
        _ctx.Request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();

The trick is without the Request.Properties entry setting, it will show an error of:

诀窍是没有 Request.Properties 条目设置,它将显示以下错误:

The request does not have an associated configuration object or the provided configuration was null.

请求没有关联的配置对象或提供的配置为空。

This might be an oversight on the part of the designers, as you can set an HTTPConfiguration in the HTTPActionContext constructor!

这可能是设计人员的疏忽,因为您可以在 HTTPActionContext 构造函数中设置 HTTPConfiguration!

回答by Seafish

Here's a working example from 2018 (.NET Framework 4.5.1). It uses an ExceptionFilterAttribute but it should be similar for other FilterAttributes.

这是 2018 年的一个工作示例(.NET Framework 4.5.1)。它使用 ExceptionFilterAttribute,但对于其他 FilterAttributes 应该类似。

[Test]
public void MyTest()
{
    var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
    var response = new HttpResponseMessage();

    // This next line is necessary to avoid the following error
    // if you call `context.Request.CreateResponse(...)` inside the filter:
    // System.InvalidOperationException: The request does not have an associated configuration object or the provided configuration was null.
    // Discovered from https://stackoverflow.com/a/44447355/3312114
    request.SetConfiguration(new HttpConfiguration());

    var context = ContextUtil.GetActionExecutedContext(request, response);

    _myFilter.OnException(context); // Execute your methods

    Assert.AreEqual(HttpStatusCode.InternalServerError, context.Response.StatusCode); // Make your assertions
}

Then just copy the ContextUtil class into your test project somewhere. @thomasb's comment on @tugberk's answer suggests the latest code is on Codeplex. While that comment was in 2014 so there may even be later code, the 2014 code worked for me (in Jan 2018) while the original linked code did not. I've copied the later version below for convenience. Just drop this into a new file.

然后只需将 ContextUtil 类复制到您的测试项目中。@thomasb 对@tugberk 的回答的评论表明最新的代码在 Codeplex 上。虽然该评论是在 2014 年,因此甚至可能有以后的代码,但 2014 年的代码对我有用(2018 年 1 月),而原始链接代码则没有。为方便起见,我复制了下面的更高版本。只需将其放入一个新文件中即可。

internal static class ContextUtil
{
    public static HttpControllerContext CreateControllerContext(HttpConfiguration configuration = null, IHttpController instance = null, IHttpRouteData routeData = null, HttpRequestMessage request = null)
    {
        HttpConfiguration config = configuration ?? new HttpConfiguration();
        IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute());
        HttpRequestMessage req = request ?? new HttpRequestMessage();
        req.SetConfiguration(config);
        req.SetRouteData(route);

        HttpControllerContext context = new HttpControllerContext(config, route, req);
        if (instance != null)
        {
            context.Controller = instance;
        }
        context.ControllerDescriptor = CreateControllerDescriptor(config);

        return context;
    }

    public static HttpActionContext CreateActionContext(HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null)
    {
        HttpControllerContext context = controllerContext ?? ContextUtil.CreateControllerContext();
        HttpActionDescriptor descriptor = actionDescriptor ?? CreateActionDescriptor();
        descriptor.ControllerDescriptor = context.ControllerDescriptor;
        return new HttpActionContext(context, descriptor);
    }

    public static HttpActionContext GetHttpActionContext(HttpRequestMessage request)
    {
        HttpActionContext actionContext = CreateActionContext();
        actionContext.ControllerContext.Request = request;
        return actionContext;
    }

    public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response)
    {
        HttpActionContext actionContext = CreateActionContext();
        actionContext.ControllerContext.Request = request;
        HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response };
        return actionExecutedContext;
    }

    public static HttpControllerDescriptor CreateControllerDescriptor(HttpConfiguration config = null)
    {
        if (config == null)
        {
            config = new HttpConfiguration();
        }
        return new HttpControllerDescriptor() { Configuration = config, ControllerName = "FooController" };
    }

    public static HttpActionDescriptor CreateActionDescriptor()
    {
        var mock = new Mock<HttpActionDescriptor>() { CallBase = true };
        mock.SetupGet(d => d.ActionName).Returns("Bar");
        return mock.Object;
    }
}