Comment unitaire Test ValueProviderFactories dans ASP.NET MVC3?
-
25-09-2019 - |
Question
Nous voulions mettre à niveau nos projets de ASP.NET MVC 2 à 3. La plupart de nos tests ont réussi, mais il y en a qui échouent sur ValueProviderFactories.Factories.GetValueProvider(context)
.
Voici une simple classe de test qui illustre le problème.
[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));
}
}
Exception "Référence d'objet non définie sur une instance d'un objet." est lancé quand ValueProviderFactories.Factories.GetValueProvider(context)
est appelé. Le stacktrace ressemble à ceci:
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#
Je voulais connaître la raison pour laquelle il lance l'exception et a vu:
public static ValidationUtility.UnvalidatedCollections GetUnvalidatedCollections(HttpContext context)
{
return (ValidationUtility.UnvalidatedCollections) context.Items[_unvalidatedCollectionsKey];
}
Alors, sommes-nous de retour dans le passé quand nous dépendons de HttpContext.Current
? Comment le stimuler?
La solution
Cela peut facilement être résolu par des valeurs prox-ing qui accèdent à HttpContext à celle l'ignorer.
J'ai tout expliqué dans mon article de blog: Actions de test unitaire avec ValueProviderFactories dans ASP.NET MVC3.
La clé est ce code:
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);
}
}
}
Il peut donc être utilisé comme:
ValueProviderFactories.Factories
.ReplaceWith<FormValueProviderFactory>(ctx => ctx.HttpContext.Request.Form)
.ReplaceWith<QueryStringValueProviderFactory>(ctx => ctx.HttpContext.Request.QueryString);
C'était en fait très facile :)
METTRE À JOUR: Comme mentionné dans les commentaires, vous devez vous rappeler:
- Positionner
ctx.HttpContext.Request.ContentType
Propriété à une valeur non nulle, sinon le JSONValueProviderFactory lancera l'exception. Je préfère créer une simulation et y régler la valeur par défaut. - remplace le
HttpFileCollectionValueProviderFactory
car il peut être utilisé pendant la liaison. - Faites attention aux autres dépendances que vous pourriez avoir dans le projet.
Autres conseils
Vous ne devriez pas appeler ValueProviderFactories.factories, ModelBinders.Binders, ou tout autre accessoire statique à partir d'un test unitaire. C'est précisément pourquoi ModelBindingContext.Valueprovider existe - afin que vous puissiez fournir un ivalueprovider ivaleur de votre propre création plutôt que de compter sur les valeurs par défaut statiques (qui supposent que le pipeline MVC est en cours d'exécution).