Как выполнить модульное тестирование ValueProviderFactories в ASP.NET MVC3? - PullRequest
9 голосов
/ 05 января 2011

Мы хотели обновить наши проекты с 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? Как обойти это?

Ответы [ 2 ]

8 голосов
/ 18 марта 2011

Эту проблему легко решить, передав прокси-серверы ValueProviders, которые обращаются к HttpContext, к тому, кто его игнорирует.

Я объяснил все в своем блоге: Действия модульного теста с ValueProviderFactories в 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);

Это было на самом деле очень просто:)

ОБНОВЛЕНИЕ : Как уже упоминалось в комментариях, вы должны помнить:

  1. установить для свойства ctx.HttpContext.Request.ContentType какое-то ненулевое значение, в противном случае JsonValueProviderFactory вызовет исключение. Я предпочитаю создать макет и установить там значение по умолчанию.
  2. замените HttpFileCollectionValueProviderFactory, так как он может использоваться во время переплета.
  3. следите за другими зависимостями, которые могут у вас быть в проекте.
0 голосов
/ 05 января 2011

Вы не должны вызывать ValueProviderFactories.Factories, ModelBinders.Binders или любой другой статический метод доступа из модульного теста.Именно поэтому существует ModelBindingContext.ValueProvider - так что вы можете предоставить имитированный IValueProvider вашего собственного создания, а не полагаться на статические значения по умолчанию (которые предполагают, что конвейер MVC работает).

...