Come unità test ValueProviderFactories in ASP.NET MVC3?
-
25-09-2019 - |
Domanda
Volevamo aggiornare i nostri progetti da ASP.NET MVC 2 a 3. La maggior parte dei nostri test è riuscita, ma ce ne sono alcuni che falliscono ValueProviderFactories.Factories.GetValueProvider(context)
.
Ecco una semplice classe di test che ilustra il problema.
[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));
}
}
Eccezione "Riferimento oggetto non impostato su un'istanza di un oggetto." viene lanciato quando ValueProviderFactories.Factories.GetValueProvider(context)
è chiamato. Lo stacktrace sembra così:
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#
Volevo sapere il motivo per cui lancia l'eccezione e ho visto:
public static ValidationUtility.UnvalidatedCollections GetUnvalidatedCollections(HttpContext context)
{
return (ValidationUtility.UnvalidatedCollections) context.Items[_unvalidatedCollectionsKey];
}
Quindi, siamo tornati nel passato quando eravamo dipendenti HttpContext.Current
? Come alterarlo?
Soluzione
Questo può essere facilmente risolto da prox-ing.
Ho spiegato tutto nel mio post sul blog: Azioni di test unitaria con ValueProviderFactories in ASP.NET MVC3.
La chiave è questo codice:
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);
}
}
}
Quindi può essere usato come:
ValueProviderFactories.Factories
.ReplaceWith<FormValueProviderFactory>(ctx => ctx.HttpContext.Request.Form)
.ReplaceWith<QueryStringValueProviderFactory>(ctx => ctx.HttpContext.Request.QueryString);
In realtà è stato molto facile :)
AGGIORNARE: Come menzionato nei commenti che dovresti ricordarti di:
- impostare
ctx.HttpContext.Request.ContentType
Proprietà a un valore non null, altrimenti JSonValueProviderFactory lancerà un'eccezione. Preferisco creare un finto e impostare il valore predefinito lì. - sostituisci il
HttpFileCollectionValueProviderFactory
come può essere usato durante il legame. - Fai attenzione ad altre dipendenze che potresti avere nel progetto.
Altri suggerimenti
Non dovresti chiamare ValueProviderFactories.Factories, Modelbinders.Binders o qualsiasi altro accessorio statico all'interno di un test unitario. Questo è proprio il motivo per cui esiste ModelbindingContext.valueProvider, in modo da poter fornire un IvalueProvider deriso della tua creazione piuttosto che fare affidamento sui predefiniti statici (che presuppongono che la pipeline MVC sia in esecuzione).