【问题标题】:How to unit test a route using custom model binder如何使用自定义模型绑定器对路由进行单元测试
【发布时间】:2011-04-11 02:43:10
【问题描述】:

我有一个自定义模型绑定器,用于检查用户是否有权访问他请求的文档;我想知道如何测试使用此自定义绑定器的路线?

我尝试了这个测试,但我得到了这个错误:

MvcContrib.TestHelper.AssertionException :参数“合同”的值确实 不匹配:预期 'Domain.Models.Contract' 但是是 ''; 在路由上下文中找不到值 名为“合同”的操作参数 - 您的匹配路线是否包含 称为“合约”的代币?

[SetUp]
public void Setup()
{
    MvcApplication.RegisterModelBinders();
    MvcApplication.RegisterRoutes(RouteTable.Routes);
}

[Test]
public void VersionEdit()
{
    var contract = TestHelper.CreateContract();
    var route = "~/Contract/" + contract.Id.ToString() + "/Version/Edit/" + 
        contract.Versions.Count;
    route.ShouldMapTo<VersionController>(c => c.Edit(contract));
}

如果我尝试调试自定义活页夹,则永远不会被调用。

我的路线定义:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "VersionToken", // Route name
                "Contract/{contractId}/Version/{version}/{action}/{token}", // URL with parameters
                new { controller = "Version", action = "ViewContract", version = 1, token = UrlParameter.Optional } // Parameter defaults
            );

            routes.MapRoute(
                "Version", // Route name
                "Contract/{contractId}/Version/{version}/{action}", // URL with parameters
                new { controller = "Version", action = "Create", version = UrlParameter.Optional } // Parameter defaults
            );

            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );

            if (HttpContext.Current != null && !HttpContext.Current.IsDebuggingEnabled) routes.IgnoreRoute("CI");
        }

我的模型活页夹:

public static void RegisterModelBinders()
        {
            var session = (ISession)DependencyResolver.Current.GetService(typeof(ISession));
            var authService = (IAuthenticationService)DependencyResolver.Current.GetService(typeof(IAuthenticationService));
            System.Web.Mvc.ModelBinders.Binders[typeof (Contract)] = new ContractModelBinder(session, authService);
        }

public class ContractModelBinder : DefaultModelBinder
    {
        private readonly ISession _session;
        private readonly IAuthenticationService _authService;
        public ContractModelBinder(ISession session, IAuthenticationService authService)
        {
            _session = session;
            _authService = authService;
        }

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var contractId = GetValue(bindingContext, "contractId");
            var version = GetA<int>(bindingContext,"version");
            var token = GetValue(bindingContext, "token");

            var contract = _session.Single<Contract>(contractId);
            if (contract == null) 
            {
                throw new HttpException(404, "Not found");
            }
            if (contract.Versions.Count < version.Value)
            {
                throw new HttpException(404, "Not found");
            }
            contract.RequestedVersionNumber = version.Value;
            if(token == null)
            {
                var user = _authService.LoggedUser();
                if (user == null) throw new HttpException(401, "Unauthorized");
                if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id)
                {
                    throw new HttpException(403, "Forbidden");
                }
            }
            else
            {
                contract.RequestedToken = token;
                var userToken = contract.RequestedVersion.Tokens.SingleOrDefault(x => x.Token == token);
                if (userToken == null)
                {
                    throw new HttpException(401, "Unauthorized");
                }
            }

            return contract;
        }

        private static T? GetA<T>(ModelBindingContext bindingContext, string key) where T : struct, IComparable
        {
            if (String.IsNullOrEmpty(key)) return null;
            //Try it with the prefix...
            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + key);
            //Didn't work? Try without the prefix if needed...
            if (valueResult == null && bindingContext.FallbackToEmptyPrefix)
            {
                valueResult = bindingContext.ValueProvider.GetValue(key);
            }
            if (valueResult == null)
            {
                return null;
            }
            return (T)valueResult.ConvertTo(typeof(T));
        }

        private static string GetValue(ModelBindingContext bindingContext, string key)
        {
            if (String.IsNullOrEmpty(key)) return null;
            //Try it with the prefix...
            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + key);
            //Didn't work? Try without the prefix if needed...
            if (valueResult == null && bindingContext.FallbackToEmptyPrefix)
            {
                valueResult = bindingContext.ValueProvider.GetValue(key);
            }
            if (valueResult == null)
            {
                return null;
            }
            return valueResult.AttemptedValue;
        }
    }

【问题讨论】:

  • 您能告诉我们您的路线定义以及您如何注册模型绑定器吗?
  • @Sergi 我添加了路由定义和模型绑定器

标签: c# unit-testing asp.net-mvc-3 asp.net-mvc-routing


【解决方案1】:

在测试路由时,MvcContrib TestHelper 不会调用 MVC 管道和模型绑定器。模型绑定器应单独进行单元测试。绑定与路由是分开的,并且在实例化控制器并调用操作时发生。

在此示例中,您正在对路线进行单元测试。因此,您只需确保 ~/Contract/5/Version/Edit/3 正确映射到您的 VersionController 上的 Edit 操作,操作如下:

"~/Contract/5/Version/Edit/3".ShouldMapTo<VersionController>(c => c.Edit(null));

【讨论】:

  • 所以我不必测试它是否映射到我的操作的正确参数?
  • @VinnyG,这是模型绑定器的工作。您应该对其进行测试,但要在与用于测试路线的单元测试不同的单元测试中进行。
  • 我理解你的意思,我已经对我的自定义活页夹进行了测试,但我不确定这是否是一个好的答案。是的,我的测试通过了,但它为什么要指向一个空参数?
  • @VinnyG,问题在于 MvcContrib TestHelper 不支持您尝试测试的场景。它适用于简单的标量参数,但不适用于自定义模型绑定器,因为它们不会被调用。
  • 当需要一个int时如何传递null?
猜你喜欢
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-27
相关资源
最近更新 更多