SpecsFor<Web> Helpers will help you write cleaner, easier-to-read (and maintain!) specs for your ASP.NET MVC applications. It’s the latest addition to the SpecsFor family, and the first of the Helpers line that I’m working on. Read on to see what it can do for you and your team!
[more]
I suppose this really doesn’t qualify as “news” anymore. SpecsFor<Web> Helpers shipped back in the fall, but in the rush to get my SpecsFor Pluralsight course completed, I failed to mention it, much less document what this library can do. Here’s a quick overview of what the library can do.
SpecsFor<Web> 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 SpesFor<Web> 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<Web> 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<Web> Helpers:
[Test] public void then_it_says_hello_to_the_user() { _result.ShouldRenderDefaultView() .WithModelLike(new SayHelloViewModel { Name = "John Doe" }); }
Testing a RedirectResult
Without SpecsFor<Web> 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<Web> 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<Web> 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<Web> Helpers:
protected override void When() { _filterContext = new FakeActionExecutingContext(); SUT.OnActionExecuting(_filterContext); }
Testing an HtmlHelper
Without SpecsFor<Web> 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<Web> 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<Web> 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
- FakeHttpRequest
- ICookieProvider
- IFormParamsProvider
- IQueryStringParamsProvider
- 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(); } }
By the way, all this information is now on the official docs, too!
This is still a work-in-progress. Let me know what pain points you encounter with this, or with MVC testing in general, and I’ll see what I can do to help!