Reg. Unit Testing an Html Helper with AutoFixture
The original question is : Unit Testing an Html Helper with AutoFixture
Not sure I should re-open the original question; however since I marked the original question as resolved I decided to create a new one. Apologies if I did this wrong.
I was using Mark’s suggested approach but I got stuck while using the Freeze.
Below is the complete source code… Class Under Test:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcDemo2.Helpers
{
public static class Keys
{
public static readonly string SomeKey = "SomeKey";
}
public static class SampleHelpers
{
public static MvcHtmlString SampleTable(this HtmlHelper helper,
SampleModel model, IDictionary<string, object> htmlAttributes)
{
if (helper == null)
{
throw new ArgumentNullException("helper");
}
if (model == null)
{
throw new ArgumentNullException("model");
}
TagBuilder tagBuilder = new TagBuilder("table");
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.GenerateId(helper.ViewContext.
HttpContext.Items[Keys.SomeKey].ToString());
return MvcHtmlString.Create(
tagBuilder.ToString(TagRenderMode.Normal));
}
}
}
public class SampleModel
{
}
}
Unit Test is to ensure the Html returned as expected for the specified key within HttpContext
public void SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml()
I configured the Fixture as below
var fixture = new Fixture().Customize(new AutoMoqCustomization());
Then the Freeze on ViewContext:
var vc = fixture.Freeze<ViewContext>();
I get 开发者_运维知识库the below exception:
Error 1 Test 'MvcDemo2.Tests.Controllers.SampleHelpersTestsAutoFixture.SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml' failed: System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation. ---- System.NotImplementedException : The method or operation is not implemented. at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) at Ploeh.AutoFixture.Kernel.AutoPropertiesCommand1.Execute(T specimen, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor
1.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.<>c__DisplayClass6.b__1(ISpecimenBuilder b) at System.Linq.Enumerable.WhereSelectListIterator2.MoveNext() at System.Linq.Enumerable.WhereSelectEnumerableIterator
2.MoveNext() at System.Linq.Enumerable.d__a51.MoveNext() at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable
1 source) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.SpecimenContext.Resolve(Object request) at Ploeh.AutoFixture.Kernel.SeedIgnoringRelay.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.<>c__DisplayClass6.b__1(ISpecimenBuilder b) at System.Linq.Enumerable.WhereSelectListIterator2.MoveNext() at System.Linq.Enumerable.WhereSelectEnumerableIterator
2.MoveNext() at System.Linq.Enumerable.d__a51.MoveNext() at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable
1 source) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.Postprocessor1.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.<>c__DisplayClass6.<Create>b__1(ISpecimenBuilder b) at System.Linq.Enumerable.WhereSelectListIterator
2.MoveNext() at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext() at System.Linq.Enumerable.<DefaultIfEmptyIterator>d__a5
1.MoveNext() at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source) at Ploeh.AutoFixture.Kernel.CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.RecursionGuard.Create(Object request, ISpecimenContext context) at Ploeh.AutoFixture.Kernel.SpecimenContext.Resolve(Object request) at Ploeh.AutoFixture.SpecimenFactory.CreateAnonymous[T](ISpecimenContext context, T seed) at Ploeh.AutoFixture.SpecimenFactory.CreateAnonymous[T](ISpecimenBuilderComposer composer, T seed) at Ploeh.AutoFixture.FixtureFreezer.Freeze[T](IFixture fixture, T seed) at MvcDemo2.Tests.Controllers.SampleHelpersTestsAutoFixture.SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml() in C:\Users\...:line 78 ----- Inner Stack Trace ----- at System.Web.HttpContextBase.get_Items() at System.Web.Mvc.ViewContext.ScopeCache.Get(IDictionary
2 scope, HttpContextBase httpContext) at System.Web.Mvc.ViewContext.SetClientValidationEnabled(Boolean enabled, IDictionary`2 scope, HttpContextBase httpContext) at System.Web.Mvc.ViewContext.set_ClientValidationEnabled(Boolean value) C:\Users... 78
Seems it fails on when it try to set the value of property ViewContext.SetClientValidationEnabled.
I’m stuck on this. Any thoughts greatly appreciated.
Well, if you consider the inner stack trace you'll notice that the exception is being thrown by HttpContextBase.Items
. If you were to look at the implementation using a reflection tool you would find that this virtual method throws a NotImplementedException - talk about a POLA violation. A lot of the Web abstractions in the BCL does exactly that, which is really, really painful to deal with.
I'm not sure it'll help you further on, but you can get past this particular issue by turning off AutoProperties for the ViewContext class like this:
fixture.Customize<ViewContext>(c => c.OmitAutoProperties());
Thanks Mark. I find AutoFixture is very useful but when I Unit Test MVC, I find bit trickier to apply the API. It is probably because I’m still new to the API.
With….
fixture.Customize<ViewContext>(c => c.OmitAutoProperties());
var vc = fixture.Freeze<ViewContext>();
The above works fine, but as you suspected it fails again when I try to add items to
vc.HttpContext.Items.Add(Keys.SomeKey, "foo");
Exceptiion: The method or operation is not implemented.
The reflector on HttpContextBase...
public virtual IDictionary Items
{
get
{
throw new NotImplementedException();
}
}
So the only way I can think is to create my own implementation of HttpContextBase
private class FakeHttpContext : HttpContextBase
{
private Dictionary<object, object> _items = new Dictionary<object, object>();
public override IDictionary Items { get { return _items; } }
}
Then below is the confusing bit..
fixture.Inject<HttpContextBase>(new FakeHttpContext());
var hc = fixture.CreateAnonymous<HttpContextBase>();
If I swap these two lines (which creates the anonymouse instance first and then inject the fake instance) causes the error..
AutoFixture was unable to create an instance from System.Web.HttpContextBase, most likely because it has no public constructor.
So far I can come up with a test like this...
public void SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml()
{
//Arrange
var fixture = new Fixture();
fixture.Customize<ViewContext>(c => c.OmitAutoProperties());
var vc = fixture.Freeze<ViewContext>();
fixture.Inject<HttpContextBase>(new FakeHttpContext());
var hc = fixture.CreateAnonymous<HttpContextBase>();
vc.HttpContext = hc;
vc.HttpContext.Items.Add(Keys.SomeKey, "foo");
var htmlHelper = fixture.CreateAnonymous<HtmlHelper>();
var sampleModel = fixture.CreateAnonymous<SampleModel>();
//Act
var result = SampleHelpers.SampleTable(htmlHelper, sampleModel, null).ToString();
//Assert
Assert.Equal("<table id=\"foo\"></table>", result);
}
Also removed new AutoMoqCustomization() as it seems to have no effect on the test. Once I get more familiar with AutoFixture I might be able to come up with a better version. Thanks for the help.
Seems it fails on when it try to set the value of property
ViewContext.SetClientValidationEnabled
.
That problem was fixed for me by this answer, see how he gets an HtmlHelper (see also here).
精彩评论