Unit Testing an Html Helper with AutoFixture

2019-05-24 13:48发布

问题:

I'm attempting to Unit Test an Html Helper using AutoFixture. Below is my SUT

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));
}

As you can see it just returns an MVC Html string with table tags and Id attached to it. (See below Unit Test result for an example)

Unit Test with AutoFixture:

[Fact]
public void SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml()
{
    var fixture = new Fixture();

    //Arrange
    fixture.Inject<HttpContextBase>(new FakeHttpContext());
    var httpContext = fixture.CreateAnonymous<HttpContextBase>();
    fixture.Inject<ViewContext>(new ViewContext());
    var vc = fixture.CreateAnonymous<ViewContext>();

    vc.HttpContext = httpContext;
    vc.HttpContext.Items.Add(Keys.SomeKey, "foo");

    fixture.Inject<IViewDataContainer>(new FakeViewDataContainer());
    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);
}      

FakeHttpContext and FakeViewDataContainer are just the fake implementations of HttpContextBase and IViewDataContainer.

This test passes and returns the expected result. However, I ‘m not sure I’m correctly utilizing the Autofixture here. Is there a better way to use AutoFixture within this Unit Test?

回答1:

Based on partial information it's hard to tell exactly how the above test could be further reduced, but I would guess that it could be reduced.

First of all, the combo of invoking Inject followed by CreateAnonymous is rather idiomatic - particularly if you reverse the sequence. This is called Freezing the anonymous value (and is equivalent to a DI container's Singleton lifetime scope). It can be stated more succinctly like this:

var vc = fixture.Freeze<ViewContext>();

It also seems as though the test is mapping HttpContext to FakeHttpContext. Mapping can be done a little bit easier, but that'll map Transient instances...

In any case, unless you have compelling reasons to use Manual Mocks instead of a dynamic Mock library, you might as well decide to use AutoFixture as an auto-mocking container. That might rid you of a lot of that type mapping.

So, given all that, I'd guess that you might be able to reduce the test to something like this:

[Fact]
public void SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());

    //Arrange
    var vc = fixture.Freeze<ViewContext>();
    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);
}

However, most of the Arrange part is now purely declarative, and since you seem to already be using xUnit.net, you can use AutoData Theories for AutoFixture to move most of the variables to method arguments:

[Theory, AutoMoqData]
public void SampleTableHtmlHelper_WhenKeyExistWithinHttpContext_ReturnsExpectedHtml(
    [Frozen]ViewContext vc,
    HtmlHelper htmlHelper,
    SampleModel sampleModel)
{
    //Arrange
    vc.HttpContext.Items.Add(Keys.SomeKey, "foo");

    //Act
    var result = SampleHelpers.SampleTable(htmlHelper, sampleModel, null).ToString();

    //Assert
    Assert.Equal("<table id=\"foo\"></table>", result);
}

This assumes that you've bridged the AutoMoqCustomization with the AutoDataAttribute like this:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute :
        base(new Fixture().Customize(new AutoMoqCustomization()))
    { }
}

Please keep in mind that you may need to tweak the above code a bit to make it fit the details of your API. This is only meant as a sketch.