actionfilter_测试MVC控制器的ActionFilter

actionfilter

你好,

这是我为bytes.com写的第二篇文章,这次我希望分享一些有关如何在ASP.NET MVC 2 Framework中测试控制器的自定义ActionFilterAttribute的知识。

从Ruby on Rails的背景开始,我已经使用框架一个月了,并且开发了一个特殊的ActionFilter,它使用定制的HTTP标头针对我们的数据库执行用户身份验证。

筛选器的概念是检查这些标头的存在,然后从这些标头中提取所需的信息以执行身份验证。 过滤器的代码看起来像这样:


using System;
using System.Text;
using System.Web.Mvc;
using TenForce.Execution.Framework;
using TenForce.Execution.Api2.Implementation; 
namespace TenForce.Execution.Web.Filters
{
    /// <summary>
    /// This class defines a custom Authentication attribute that can be applied on controllers.
    /// This results in authentication occuring on all actions that are beeing defined in the controller
    /// who implements this filter.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthenticationFilter : ActionFilterAttribute
    {
        #region IAuthorizationFilter Members 
        /// <summary>
        /// This function get's called by the Mvc framework prior to performing any actions on
        /// the controller. The function will check if a call is authorized by the caller.
        /// The function will extract the username and password from the HTTP headers send by
        /// the caller and will validate these against the database to see if there is a valid
        /// account for the user.
        /// If the user can be found in the database, operations will resume, otherwise the action
        /// is canceled.
        /// </summary>
        /// <param name="filterContext">The context for the filter.</param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // Call the base operations first.
            base.OnActionExecuting(filterContext); 
            // Surround the entire authentication process with a try-catch to prevent errors from
            // breaking the code.
            try
            {
                // Extract the custom authorization header from the HTTP headers.
                string customAuthHeader = Encoding.UTF8.GetString(Convert.FromBase64String(filterContext.RequestContext.HttpContext.Request.Headers["TenForce-Auth"])); 
                // Split the header in the subcomponents.
                string[] components = customAuthHeader.Split('|'); 
                // Check if both components are present.
                if (components.Length >= 2)
                {
                    // This header consists of 2 parts, the username and password, seperate by a vertical pipe.
                    string username = components[0] ?? string.Empty;
                    string password = components[1] ?? string.Empty; 
                    // Validate the user against the database.
                    if (Authenticator.Authenticate(username, password))
                    {
                        // The request is valid, so add the custom header to inform the request was
                        // authorized.
                        AllowRequest(filterContext);
                        return;
                    }
                } 
                // If we reach this point, the authorization request is no longer valid.
                CancelRequest(filterContext);
            }
            catch (Exception ex)
            {
                // Log the exception that has occured.
                Logger.Log(GetType(), ex); 
                // Cancel the request, as we could not properly process it.
                CancelRequest(filterContext);
            }
        } 
        #endregion 
        #region Private Methods 
        /// <summary>
        /// Cancels the Athorization and adds the custom tenforce header to the response to
        /// inform the caller that his call has been denied.
        /// </summary>
        /// <param name="authContext">The authorizationContxt that needs to be canceled.</param>        
        private static void CancelRequest(ActionExecutingContext authContext)
        {
            authContext.Result = new HttpUnauthorizedResult();
            authContext.HttpContext.Response.Headers.Add(@"Custom Response Header", @"Denied value");
        } 
        /// <summary>
        /// Allows the Authorization and adds the custom tenforce header to the response to
        /// inform the claler that his call has been allowed.
        /// </summary>
        /// <param name="authContext">The authorizationContext that needs to be allowed.</param>
        private static void AllowRequest(ActionExecutingContext authContext)
        {
            authContext.Result = null;
            authContext.HttpContext.Response.Headers.Add(@"Custom Response Header", @"Accepted Value");
        }         
        #endregion
    }
} 
该代码说明了它的目的。 但是,因为我们使用TDD,所以我们需要一个UnitTest,它实际上可以模拟此过滤器的必需条件并提供正确的属性,而该过滤器不知道它正在使用模拟数据。

这个问题的答案:模拟框架。

对于以下代码,我们将依赖以下框架来正确支持代码:

-最小起订量4

-吉利奥

-我们自己的框架(处理数据库流量)

步骤:1构造模拟对象

正确测试上面显示的ActionFilter功能的第一步是构造将代表HttpContext对象的Mock对象。 在MVC框架中,此对象包含与特定HTTP请求或响应有关的所有信息。 但是,棘手的部分是,各种属性是只读的,如果不在其周围构造巨大的包装器,则不能直接设置它们。

因此,为了构造对象,响应和请求,我们使用来自Mock框架的以下调用:

HttpRequest = new Mock<HttpRequestBase>();
            HttpResponse = new Mock<HttpResponseBase>();
            HttpContext = new Mock<HttpContextBase>();
            ActionContext = new Mock<ActionExecutingContext>();
            Filter = new Web.Filters.AuthenticationFilter();
请注意,这些值存储在Test类的属性中。

这些对象本身并没有做什么用。 我们需要告诉这些Mock对象如何响应来自Filter的请求,否则我们将被空引用异常轰炸。

当您查看过滤器的代码时,就可以证明我们需要访问以下部分:

-HttpContext的Response和Request属性

-Response对象的Headers属性

-Request对象的Headers属性

-用于请求身份验证的网址

因为每个对象都是全局ActionContext对象的子对象,所以我们需要逐步构建请求树。 以下代码演示了如何将对象链接在一起:


ActionContext.SetupGet(c => c.HttpContext).Returns(HttpContext.Object);
            HttpContext.SetupGet(r => r.Request).Returns(HttpRequest.Object);
            HttpContext.SetupGet(r => r.Response).Returns(HttpResponse.Object);
            HttpResponse.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection());
            HttpRequest.SetupGet(r => r.RawUrl).Returns(@"http://test.yourdomain.com");
现在,我们已经将Mock对象配置为彼此了解并返回在调用Headers,Request或Response属性时可以使用的东西。

步骤2:执行单元测试

现在我们需要实际调用测试。 因为过滤器是一个类,所以我们可以从中创建一个实例,并使用UnitTest框架(例如NUnit)运行代码:


[Test]
        public void SuccessfullAuthentication()
        {
            // Configure the Request and Response headers before making the call
            // to the ActionFilter. Ensure the authentication header is present.
            HttpRequest.SetupGet(r => r.Headers).Returns(new System.Net.WebHeaderCollection
                                                             {{@"YourHeader", "Header value"}}); 
            // Call the action on the filter and check the response.
            Filter.OnActionExecuting(ActionContext.Object); 
            // Check the ActionResult to null and that the response header contains the correct value.
            Assert.IsTrue(ActionContext.Object.Result == null);
            Assert.IsTrue(ActionContext.Object.HttpContext.Response.Headers["Response Header"].Equals(@"Response Header Value"));
        } 
该代码的作用是调用当接收到需要身份验证的请求时MVC框架将调用的函数。

在第一行代码中,我们模仿HTTP请求的调用,并使用正确的值插入ActionFilter所需的自定义标头。

第二行代码调用实际的过滤器代码,并执行整个验证过程,这可以在第一个代码段中看到。

最困难的部分是正确配置Mock框架,以模仿MVC调用。 因为我自己为此苦苦挣扎,而且解决方案太简单了,所以我决定与所有人分享这一见解。

翻译自: https://bytes.com/topic/asp-net/insights/905132-testing-actionfilter-mvc-controller

actionfilter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值