SpecsFor<Web> Helpers

One of the benefits of ASP.NET MVC over WebForms is that you can actually write automated unit tests against large pieces of your web applications. However, testing ASP.NET MVC applications isn't as straight-forward as it could be. The out-of-the-box experience leads to complex test cases.

SpecsFor Helpers adds additional assertions and fake objects that will help you write cleaner, easier-to-read specs for Controllers, Action Filters, HtmlHelpers, and more.

The utility of SpecsFor Helpers is easiest to see when you compare it to specs that aren't using them. Check out the examples below!

Testing a ViewResult

Without SpecsFor Helpers:

[Test]
public void then_it_says_hello_to_the_user()
{
  var viewResult = _result.ShouldBeType<ViewResult>();
  var model = viewResult.Model.ShouldBeType<SayHelloViewModel>();
  model.ShouldLookLike(new SayHelloViewModel
                       {
                         Name = "John Doe"
                       });
}

With SpecsFor Helpers:

[Test]
public void then_it_says_hello_to_the_user()
{
  _result.ShouldRenderDefaultView()
    .WithModelLike(new SayHelloViewModel
                   {
                     Name = "John Doe"
                   });
}

Testing a RedirectResult

Without SpecsFor Helpers:

[Test]
public void then_it_redirects_to_the_say_hello_action()
{
  var redirectResult = _result.ShouldBeType<RedirectToRouteResult>();
  redirectResult.RouteValues["controller"].ShouldEqual("Home");
  redirectResult.RouteValues["action"].ShouldEqual("SayHello");
  redirectResult.RouteValues["name"].ShouldEqual("Jane Doe");
}

With SpecsFor Helpers:

[Test]
public void then_it_redirects_to_the_say_hello_action()
{
  _result.ShouldRedirectTo<HomeController>(
    c => c.SayHello("Jane Doe"));
}

Testing an Action Filter

Without SpecsFor Helpers:

protected override void When()
{
  var httpContext = new Mock<HttpContextBase>().Object;
  var controllerContext = new ControllerContext(httpContext, new RouteData(), new Mock<ControllerBase>().Object);
  var reflectedActionDescriptor = new ReflectedActionDescriptor(typeof(ControllerBase).GetMethods()[0], "Test", new ReflectedControllerDescriptor(typeof(ControllerBase)));
  _filterContext = new ActionExecutingContext(controllerContext, reflectedActionDescriptor, new Dictionary<string, object>());
  SUT.OnActionExecuting(_filterContext);
}

With SpecsFor Helpers:

protected override void When()
{
  _filterContext = new FakeActionExecutingContext();
  SUT.OnActionExecuting(_filterContext);
}

Testing an HtmlHelper

Without SpecsFor Helpers:

public class when_creating_a_bootstrap_button : SpecsFor<HtmlHelper>
{
  private HtmlTag _button;

  protected override void InitializeClassUnderTest()
  {
    var contextMock = new Mock<HttpContextBase>();
    contextMock.Setup(x => x.Request).Returns(new Mock<HttpRequestBase>().Object);
    var response = new Mock<HttpResponseBase>();
    response.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(s => s);
    contextMock.Setup(x => x.Response).Returns(response.Object);
    var viewContext = new ViewContext()
    {
      HttpContext = contextMock.Object
    };
    var viewDataContainer = new Mock<IViewDataContainer>().Object;
    SUT = new HtmlHelper(viewContext, viewDataContainer);
  }

  protected override void When()
  {
    _button = SUT.BootstrapButton("Submit!");
  }

  [Test]
  public void then_it_creates_submit_button()
  {
    _button.Attr("type").ShouldEqual("submit");
  }

  [Test]
  public void then_it_sets_the_correct_button_classes()
  {
    _button.HasClass("btn").ShouldBeTrue();
    _button.HasClass("btn-primary").ShouldBeTrue();
  }
}

With SpecsFor Helpers:

public class when_creating_a_bootstrap_button : SpecsFor<FakeHtmlHelper>
{
  private HtmlTag _button;

  protected override void When()
  {
    _button = SUT.BootstrapButton("Submit!");
  }

  [Test]
  public void then_it_creates_submit_button()
  {
    _button.Attr("type").ShouldEqual("submit");
  }

  [Test]
  public void then_it_sets_the_correct_button_classes()
  {
    _button.HasClass("btn").ShouldBeTrue();
    _button.HasClass("btn-primary").ShouldBeTrue();
  }
}

There's more...

SpecsFor Helpers includes a slew of fake objects you can use, many of which can be configured using SpecsFor's GetMockFor method (even though they aren't actually mock objects!)

Here's a full list of the available fake objects and their corresponding GetMockFor-compatible configuration interface (where applicable):

  • FakeActionExecutingContext
  • FakeHtmlHelper
  • FakeHttpContext
  • IFormParamsProvider
  • IHttpContextBehavior
  • IQueryStringParamsProvider
  • IServerVariablesParamsProvider
  • IHeadersParamsProvider
  • FakeHttpRequest
  • ICookieProvider
  • IFormParamsProvider
  • IQueryStringParamsProvider
  • IServerVariablesParamsProvider
  • IHeadersParamsProvider
  • FakeHttpSessionState
  • FakeIdentity
  • FakePrincipal
  • FakeRequestContext
  • FakeUrlHelper
  • FakeViewContext
  • FakeViewDataContainer

In many cases, you can use the fake objects as-is in your specs. If you need to configure the behavior of a fake, such as wiring up query string parameters when you're using a FakeHtmlHelper, you can grab the behavior interface using GetMockFor, then configure the behavior that the fake will use:

public class when_getting_the_app_version_and_the_app_is_in_debug_mode : SpecsFor<FakeHtmlHelper>
{
  private string _version;

  protected override void Given()
  {
    GetMockFor<IHttpContextBehavior>()
      .Setup(x => x.IsDebuggingEnabled)
      .Returns(true);
  }

  protected override void When()
  {
    _version = SUT.GetVersionString();
  }

  [Test]
  public void then_the_version_contains_the_debug_suffix()
  {
    _version.EndsWith("DEBUG").ShouldBeTrue();
  }
}