【问题标题】:How to unit test ValueProviderFactories in ASP.NET MVC3?如何在 ASP.NET MVC3 中对 ValueProviderFactories 进行单元测试?
【发布时间】:2011-01-05 11:55:35
【问题描述】:

我们想将我们的项目从 ASP.NET MVC 2 升级到 3。我们的大多数测试都成功了,但也有一些在 ValueProviderFactories.Factories.GetValueProvider(context) 上失败了。

这是一个说明问题的简单测试类。

[TestFixture]
public class FailingTest
{
  [Test]
  public void Test()
  {
    var type = typeof(string);
    // any controller
    AuthenticationController c = new AuthenticationController();
    var httpContext = new Mock<HttpContextBase>();
    var context = c.ControllerContext = new ControllerContext(httpContext.Object, new RouteData(), c);

    IModelBinder converter = ModelBinders.Binders.GetBinder(type);
    var bc = new ModelBindingContext
    {
      ModelName = "testparam",
      ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, type),
      ValueProvider = ValueProviderFactories.Factories.GetValueProvider(context)
    };
    Console.WriteLine(converter.BindModel(context, bc));
  }
}

异常“对象引用未设置为对象的实例。”在调用 ValueProviderFactories.Factories.GetValueProvider(context) 时抛出。堆栈跟踪如下所示:

Microsoft.Web.Infrastructure.dll!Microsoft.Web.Infrastructure.DynamicValidationHelper.ValidationUtility.CollectionReplacer.GetUnvalidatedCollections(System.Web.HttpContext context) + 0x23 bytes   
Microsoft.Web.Infrastructure.dll!Microsoft.Web.Infrastructure.DynamicValidationHelper.ValidationUtility.GetUnvalidatedCollections(System.Web.HttpContext context, out System.Collections.Specialized.NameValueCollection form, out System.Collections.Specialized.NameValueCollection queryString, out System.Collections.Specialized.NameValueCollection headers, out System.Web.HttpCookieCollection cookies) + 0xbe bytes    
System.Web.WebPages.dll!System.Web.Helpers.Validation.Unvalidated(System.Web.HttpRequest request) + 0x73 bytes  
System.Web.WebPages.dll!System.Web.Helpers.Validation.Unvalidated(System.Web.HttpRequestBase request) + 0x25 bytes  
System.Web.Mvc.DLL!System.Web.Mvc.FormValueProviderFactory..ctor.AnonymousMethod__0(System.Web.Mvc.ControllerContext cc) + 0x5a bytes   
System.Web.Mvc.DLL!System.Web.Mvc.FormValueProviderFactory.GetValueProvider(System.Web.Mvc.ControllerContext controllerContext) + 0xa0 bytes    
System.Web.Mvc.DLL!System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider.AnonymousMethod__7(System.Web.Mvc.ValueProviderFactory factory) + 0x4a bytes  
System.Core.dll!System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Web.Mvc.ValueProviderFactory,<>f__AnonymousType2<System.Web.Mvc.ValueProviderFactory,System.Web.Mvc.IValueProvider>>.MoveNext() + 0x24d bytes   
System.Core.dll!System.Linq.Enumerable.WhereSelectEnumerableIterator<<>f__AnonymousType2<System.Web.Mvc.ValueProviderFactory,System.Web.Mvc.IValueProvider>,System.Web.Mvc.IValueProvider>.MoveNext() + 0x2ba bytes 
mscorlib.dll!System.Collections.Generic.List<System.Web.Mvc.IValueProvider>.List(System.Collections.Generic.IEnumerable<System.Web.Mvc.IValueProvider> collection) + 0x1d8 bytes    
System.Core.dll!System.Linq.Enumerable.ToList<System.Web.Mvc.IValueProvider>(System.Collections.Generic.IEnumerable<System.Web.Mvc.IValueProvider> source) + 0xb5 bytes 
System.Web.Mvc.DLL!System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(System.Web.Mvc.ControllerContext controllerContext) + 0x24d bytes 
test.DLL!FailingTest.Test() Line 31 + 0xf9 bytes    C#

我想知道它抛出异常的原因并看到:

public static ValidationUtility.UnvalidatedCollections GetUnvalidatedCollections(HttpContext context)
{
    return (ValidationUtility.UnvalidatedCollections) context.Items[_unvalidatedCollectionsKey];
}

那么,我们是否回到了依赖HttpContext.Current 的过去?如何解决?

【问题讨论】:

  • 我也有同样的问题。 +1 提出一个好问题。
  • 我也有同样的需要,谢谢。值得一提的是,仅设置 new RouteData() 仍然会引发错误。为了克服它,我必须在提供它之前向路由数据添加一个“控制器”和“动作”键/值。

标签: asp.net asp.net-mvc unit-testing asp.net-mvc-3 httpcontext


【解决方案1】:

这可以通过代理访问 HttpContext 到忽略它的 ValueProviders 来轻松解决。

我已经在我的博文中解释了所有内容:Unit test actions with ValueProviderFactories in ASP.NET MVC3

关键是这段代码:

public static class ValueProviderFactoresExtensions {
    public static ValueProviderFactoryCollection ReplaceWith<TOriginal>(this ValueProviderFactoryCollection factories, Func<ControllerContext, NameValueCollection> sourceAccessor) {
        var original = factories.FirstOrDefault(x => typeof(TOriginal) == x.GetType());
        if (original != null) {
            var index = factories.IndexOf(original);
            factories[index] = new TestValueProviderFactory(sourceAccessor);
        }
        return factories;
    }

    class TestValueProviderFactory : ValueProviderFactory {
        private readonly Func<ControllerContext, NameValueCollection> sourceAccessor;


        public TestValueProviderFactory(Func<ControllerContext, NameValueCollection> sourceAccessor) {
            this.sourceAccessor = sourceAccessor;
        }


        public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
            return new NameValueCollectionValueProvider(sourceAccessor(controllerContext), CultureInfo.CurrentCulture);
        }
    }        
}

所以它可以用作:

ValueProviderFactories.Factories
    .ReplaceWith<FormValueProviderFactory>(ctx => ctx.HttpContext.Request.Form)
    .ReplaceWith<QueryStringValueProviderFactory>(ctx => ctx.HttpContext.Request.QueryString);

其实很简单:)

更新:如 cmets 中所述,您应该记住:

  1. ctx.HttpContext.Request.ContentType 属性设置为某个非空值,否则JsonValueProviderFactory 将抛出异常。我更喜欢在那里创建一个模拟并设置默认值。
  2. 替换HttpFileCollectionValueProviderFactory,因为它可以在绑定期间使用。
  3. 注意项目中可能存在的其他依赖项。

【讨论】:

  • 干得好!注意:您必须将 ctx.HttpContext.Request.ContentType 属性设置为某个非空值,否则 JsonValueProviderFactory 将抛出异常。
  • 我还必须替换 HttpFileCollectionValueProviderFactory 才能使其正常工作。
【解决方案2】:

您不应在单元测试中调用 ValueProviderFactories.Factories、ModelBinders.Binders 或任何其他静态访问器。这正是 ModelBindingContext.ValueProvider 存在的原因 - 这样您就可以提供自己创建的模拟 IValueProvider 而不是依赖静态默认值(假设 MVC 管道正在运行)。

【讨论】:

  • 嘲笑IValueProvider 是一项荒谬的工作。它将需要重新实现数十个 ASP.NET MVC 功能。以前它是如何可测试的,这甚至都不好笑,现在它不是了。
猜你喜欢
  • 2011-12-19
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多