SparkyTestHelpers: AspNetMvc
Tags: csharp, dotnet, testing, unittesting
Published: 2020-02-28 23:09:09

> .NET Framework version:
[NuGet package](https://www.nuget.org/packages/SparkyTestHelpers.AspNetMvc) | [Source code](https://github.com/BrianSchroer/sparky-test-helpers/tree/master/SparkyTestHelpers.AspNetMvc) | [API documentation](https://github.com/BrianSchroer/sparky-test-helpers/blob/master/SparkyTestHelpers.AspNetMvc/api.md)

> .NET Core version:
[NuGet package](https://www.nuget.org/packages/SparkyTestHelpers.AspNetMvc.Core) | [Source code](https://github.com/BrianSchroer/sparky-test-helpers/tree/master/SparkyTestHelpers.AspNetMvc.Core) | [API documentation](https://github.com/BrianSchroer/sparky-test-helpers/blob/master/SparkyTestHelpers.AspNetMvc.Core/api.md)

These NuGet packages (one for the “classic” ASP.NET MVC Framework and one for ASP.NET Core MVC) contain helpers for testing that MVC controller actions return the expected results:
- - -

## Controller Testers
**ControllerTester**<*T*> tests action methods of controllers that inherit from System.Web.MVC.**Controller** / Microsoft.AspNetCore.Mvc.**ControllerBase**.

**ApiControllerTester**<*T*> tests action methods of controllers that inherit from the .NET Framework System.Web.Http.**ApiController** class.

The general syntax is:

*tester*
&nbsp;&nbsp;&nbsp;&nbsp;.**Action**(*action selection expression*)
&nbsp;&nbsp;&nbsp;&nbsp;.**When**(*optional code to "arrange" test conditions*)
&nbsp;&nbsp;&nbsp;&nbsp;.**Expecting**...(*optional code to set up assert expectations*)
&nbsp;&nbsp;&nbsp;&nbsp;.**Test**...()

### Examples

```csharp
//ControllerTester:

    moviesControllerTester
        .Action(x => x.Index)
        .TestView();

    moviesControllerTester
        .Action(x => () => x.Details(3))
        .ExpectingViewName("Details")
        .ExpectingModel<Movle>(movie => Assert.AreEqual("Office Space", movie.Title))
        .TestView();

    moviesControllerTester
        .Action(x => x.Edit(testInvalidModel))
        .WhenModelStateIsValidEquals(false)
        .TestRedirectToAction("Errors");

    moviesControllerTester
        .Action(x => () => x.Edit(testValidModel))
        .WhenModelStateIsValidEquals(true)
        .ExpectingViewName("UpdateSuccessful")
        .TestRedirectToRoute("Home/UpdateSuccessful");

//ApiControllerTester:

   moviesApiControllerTester 
        .Action<IEnumerable<Movie>>(x => x.GetAllMovies)
        .Test(movies => Assert.AreEqual(100, movies.Count());

   moviesApiControllerTester 
        .Action(x => () => x.Get)
        .WhenRequestHasQueryStringParameters(new QueryStringParameter("id", 3))
        .TestOkNegotiatedContentResult<Movie>(movie => Assert.AreEqual("Office Space", movie.Title));

    moviesApiControllerTester
        .Action(x => () => x.Update(updateModel))
        .WhenModelStateIsValidEquals(false)
        .TestBadRequestResult();

    moviesApiControllerTester
        .Action(x => () => x.Update(updateModel))
        .WhenModelStateIsValidEquals(true)
        .TestOkResult();
```

### Instantiation
**ControllerTester**<*T*> and **ApiControllerTester**<*T*> can be created either via their constructors:
```csharp
var homeControllerTester = new ControllerTester<HomeController>(testHomeController);
var moviesApiControllerTester = new ApiControllerTester<MoviesApiController>(testMoviesController);
```
...or via **CreateTester** extension methods:
```csharp
var homeControllerTester = testHomeController.CreateTester();
var moviesApiControllerTester = testMoviesController.CreateTester();
```
### "Action" Methods

*tester*.**Action** methods use lambda expressions to specify the controller action to be tested. The expressions enable Intellisense completion when you "dot on" to the controller argument.

The syntax for parameterless actions is "*.Action(controller => controller*.**actionName**)", e.g. `.Action(x => x.Index)`.

For actions with parameters, the syntax is "*.Action(controller => () => controller*.**actionName**(*arguments*))", e.g. `.Action(x => () => x.Get(3))`.

### "When" methods
* **WhenRequestHasQueryStringParameters**(*string siteUrlPrefix, NameValueCollection queryStringParameters*)
* **WhenRequestHasQueryStringParameters**(*NameValueCollection queryStringParameters*)
* **WhenRequestHasQueryStringParameters**(*string siteUrlPrefix, params QueryStringParameter[] queryStringParameters*)
* **WhenRequestHasQueryStringParameters**(*params QueryStringParameter[] queryStringParameters*)

The **WhenRequestHasQueryStringParameters** methods (**ApiControllerTester** only) set up the controller's **Request** property with a **RequestUri** containing the specified parameters. If the **siteUrlPrefix** isn't specified (it usually won't matter in unit tests), the value "http://localhost" is used.
 
* **WhenModelStateIsValidEquals**(*bool isValid*)

This method sets up the controller's **ModelState** for testing.

* **When**(*Action action*)

This method can be used to "arrange" any conditions necessary for the test, e.g. setting up a mocked interface method.

### "Expecting" methods (**ControllerTester** only):
* **ExpectingViewName**(string expectedViewName)

**ExpectingViewName** sets up automatic validation when followed by **TestView** or **TestPartialView**

* **ExpectingModel<*TModel*>**(*Action<*TModel*> validate*) 

**ExpectingModel** sets up automatic model type and possibly content (the **validate** callback action is optional) validation when followed by **TestView** or **TestJson**.

### "Test" methods - ControllerTester:

**ControllerTester**<*TController*> has several .Test... methods used to assert that the controller action returns the expected ActionResult implementation (The method name suffixes correspond to ...Result types (e.g. **TestView** tests **ViewResult**). There are methods for all of the standard result types, plus the generic TestResult<*TActionResultType*> method.

The *validate* "callback" actions, which can be used to validate the result contents, are optional:

* **TestContent**(*Action*<*ContentResult*> *validate*)
* **TestEmpty**(*Action*<*EmptyResult*> *validate*)
* **TestFile**(*Action*<*FileResult*> *validate*)
* **TestJson**(*Action*<*JsonResult*> *validate*)
* **TestPartialView**(*Action*<*PartialViewResult*> *validate*)
* **TestRedirect**(*string expectedUrl, Action*<*RedirectResult*> *validate*)
* **TestRedirectToAction**(*string expectedActionName, Action*<*RedirectToRouteResult*> *validate*)
* **TestRedirectToRoute**(*string expectedRoute, Action*<*RedirectToRouteResult*> *validate*)
* **TestView**(*Action*<*ViewResult*> *validate*)
* **TestResult<*TActionResultType*>**(*Action*<*TActionResultType*> *validate*)

### "Test" methods - ApiControllerTester - for actions that return an IHttpActionResult:

There several **.Test**... methods used to assert that API controller actions return the expected **IHttpActionResult** implementation. There are methods for all of the standard result types, plus the generic **TestResult<*THttpActionResultType*>** method:

The *validate* "callback" actions, which can be used to validate the result contents, are optional:

* **TestBadRequestErrorMessageResult**(*Action*<*BadRequestErrorMessageResult*> *validate*)
* **TestBadRequestResult**(*Action*<*BadRequestResult*> *validate*)
* **TestConflictResult**(*Action*<*ConflictResult*> *validate*)
* **TestCreatedAtRouteNegotiatedContentResult<*T*>**(*Action*<*CreatedAtRouteNegotiatedContentResult*<*T*>> *validate*)
* **TestCreatedNegotiatedContentResult<*T*>**(*Action*<*CreatedNegotiatedContentResult*<*T*>> *validate*)
* **TestExceptionResult**(*Action*<*ExceptionResult*> *validate*)
* **TestFormattedContentResult**<*T*>(*Action<**FormattedContentResult*<*T*>> *validate*)
* **TestInternalServerErrorResult**(*Action*<*InternalServerErrorResult*> *validate*)
* **TestInvalidModelStateResult**(*Action*<*InvalidModelStateResult*> *validate*)
* **TestJsonResult**<*T*>(*Action*<*JsonResult*<*T*>> *validate*)
* **TestNegotiatedContentResult**<*T*>(*Action*<*NegotiatedContentResult*<*T*>> *validate*)
* **TestNotFoundResult**(*Action*<*NotFoundResult*> *validate*)
* **TestOkNegotiatedContentResult**<*T*>(*Action*<*OkNegotiatedContentResult*<*T*>> *validate*)
* **TestOkResult**(*Action*<*OkResult*> *validate*)
* **TestRedirectResult**(*Action*<*RedirectResult*> *validate*)
* **TestRedirectToRouteResult**(*Action*<*RedirectToRouteResult*> *validate*)
* **TestResponseMessageResult**(*Action*<*ResponseMessageResult*> *validate*)
* **TestStatusCodeResult**(*Action*<*StatusCodeResult*> *validate*)
* **TestUnauthorizedResult**(*Action*<*UnauthorizedResult*> *validate*)
* **TestResult<*THttpActionResultType*>**(*Action*<*THttpActionResultType*> *validate*)

### "Test" methods - ApiControllerTester - for actions that return an HttpResponseMessage:

The *validate* "callback" actions, which can be used to validate the result contents, are optional:

* **Test**(*Action*<*HttpResponseMessage*> *validate*)

...calls controller action, validates *HttpResponseMessage.StatusCode* (if **ExpectingHttpStatusCode(*HttpStatusCode*)** has been called) and returns the **HttpResponseMessage** returned from the action.

* **TestContentString**(*Action*<*string*> *validate*)

...calls controller action, validates *HttpResponseMessage.StatusCode* (if **ExpectingHttpStatusCode(*HttpStatusCode*)** has been called) and returns the **HttpResponseMessage.Content** string.

* **TestContentJsonDeserialization<*TContent*>**(*Action*<*TContent*> *validate*)

...calls controller action, validates *HttpResponseMessage.StatusCode* (if **ExpectingHttpStatusCode(*HttpStatusCode*)** has been called) and returns the **HttpResponseMessage.Content**'s JSON string deserialized to a *TContent* instance.

- - -

##Testing ASP.NET MVC Core RazorPages

###PageModelTester<*TPageModel*>

ASP.NET MVC Razor Page **PageModel**s have a lot in common with Controllers (they kind of combine the “C” and “M” of MVC), so the Razor **PageModelTester** has similar **Action**, **When**..., **ExpectingModel** and **Test*... methods:

**Test**… methods assert that the PageModel action returns the expected **IActionResult** implementation. There are methods for many standard result types, plus the generic **TestResult**<*TActionResultType*> method:

- **TestContent**(*(optional) Action<_ContentResult_> validate*)
- **TestFile**(*(optional) Action<_FileResult_> validate*)
- **TestJsonResult**(*(optional) Action<_JsonResult_> validate*)
- **TestPage**(*(optional) Action<_PageResult_> validate*)
- **TestRedirectToAction**(*string expecteActionName, string expectedControllerName, object expectedRouteValues, (optional) Action<RedirectToRouteResult> validate*)
- **TestRedirectToPage**(*string expectedPageName, (optional) Action<_RedirectToPageResult_> validate*)
- **TestRedirectToRoute**(*string expectedRoute, (optional) Action<_RedirectToRouteResult_> validate*)
- **TestResult<_TActionResultType_>**(*(optional) Action<_TActionResultType_> validate*)

The *validate* “callback” methods may be used for additional data assertions, beyond testing proper return types.

###Examples:

```csharp
var homeModel = new HomeModel(/* with test dependencies */);

var pageTester = new PageModelTester<HomeModel>(homeModel);

pageTester
    .Action(x => x.OnGet)
    .ExpectingModel(model => Assert.IsTrue(model.Foo))
    .TestPage();

pageTester
    .Action(x => x.OnPost)
    .WhenModelStateIsValidEquals(false)
    .ExpectingModel(model => 
        Assert.AreEqual(expectedErrorMessage, model.ErrorMessage))
    .TestPage();

pageTester
    .Action(x => x.Post)
    .WhenModelStateIsValidEquals(true)
    .TestRedirectToPage("UpdateSuccessful");
```
- - -
##Testing ASP.NET MVC Core ViewComponents

The .NET Core framework for ASP.NET MVC has another nice new tool that "framework" MVC doesn't: **ViewComponents**, a powerful alternative to partial views.

The **ViewComponent** test helper classes have a lot of similarities with their **Controller** and Razor **PageModel** counterparts:

##ViewComponentTester<TViewComponent> methods

###Invocation methods
Similar to the ControllerTester and PageModelTester "Action" methods:

- **Invocation**(*synchronous Invoke method expression*)
- **Invocation**(*async InvokeAsync method expression*)

### "When" methods
 
* **WhenModelStateIsValidEquals**(*bool isValid*)

This method sets up the ViewComponent's **ModelState** for testing.

* **When**(*Action action*)

This method can be used to "arrange" any conditions necessary for the test, e.g. setting up a mocked interface method.

###Expecting methods

- .**ExpectingViewName**(*string expectedViewName*) — used with .TestView
- **ExpectingModel<_TModelType_>**(*Action<_TModelType_> validate*) — used with .**TestView**

###Test methods

- **TestContent**(*Action<_ContentViewComponentResult_> validate*)
- **TestHtmlContent**(*Action<_HtmlContentViewComponentResult_> validate*)
- **TestView**(*Action<_ViewViewComponentResult_> validate*)
- **TestResult<_TViewComponentResultType_>**(*Action<_TViewComponentResultType_> validate*)
-  **WhenModelStateIsValidEquals**(*bool isValid*) — used to test conditional logic based on ModelState.IsValid

All *validate* “callback” actions shown above are optional.

###Example code
```csharp
using SparkyTestHelpers.AspNetMvc
. . .

new ViewComponentTester<FooViewComponent>()
    .Invocation(x => x.Invoke)
    .ExpectingViewName("Default")
    .ExpectingModel(model => Assert.IsTrue(model.Baz))
    .TestView();

new ViewComponentTester<FooViewComponent>()
    .Invocation(x => x.Invoke)
    .WhenModelStateIsValidEquals(false)
    .ExpectingViewName("Errors")
    .TestView();

new ViewComponentTester<BarViewComponent>()
    .Invocation(x => x.InvokeAsnyc)
    .TestView();
```
- - -
##Testing Routing

> These test helpers are only in the .NET Framework NuGet package. Routing has been rewritten for .NET Core, and I’ll have to figure out how to mock a bunch of different dependencies before I can add routing test helpers for .NET Core. It looks like Ivaylo Kenov has figured it out, so if you want to unit test ASP.NET Core routing now, I suggest you look at [his GitHub repo](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc).

###RouteTester

**RouteTester** and **RoutingAsserter** provide methods to assert that a given relative URL maps to the expected [RouteData.Values](https://docs.microsoft.com/en-us/dotnet/api/system.web.routing.routedata.values?view=netframework-4.7#System_Web_Routing_RouteData_Values). The **RoutingAsserter.AssertMapTo** overloads provide multiple ways to specify the expected values…

####RouteTester Constructors

- public **RouteTester**((Action<_RouteCollection_> routeRegistrationMethod*)
- public **RouteTester**(*AreaRegistration areaRegistration*)

```csharp
using SparkyTestHelpers.AspNetMvc.Routing;
. . .
  var routeTester = new RouteTester(RouteConfig.RegisterRoutes);
  var areaRouteTester = new RouteTester(new FooAreaRegistration());
```

####RouteTester Methods
- .**ForUrl**(*string relativeUrl*) — creates a new ##RoutingAsserter## instance.


###RoutingAsserter

####Nethods

- **AssertMapTo**(*IDictionary<_string, object_> expectedValues*)
- **AssertMapTo**(*object routeValues*)
- **AssertMapTo**(*string controller, string action, (object id)*) — id defaults to null
- **AssertMapTo<_TController_>**(*Expression<_Func_<_TController, Func_<_ActionResult_>_>_> actionExpression*)
- **AssertRedirectTo**(*string expectedUrl, (HttpStatusCode expectedStatusCode)*) — expectedStatusCode defaults to HttpStatusCode.Redirect (302)

####Examples

```csharp
routeTester.ForUrl("Default.aspx")
  .AssertRedirectTo("Home/LegacyRedirect");

// alternate syntaxes for asserting Home/Index routing:

routeTester.ForUrl("Home/Index")
  .AssertMapTo(new Dictionary<string, object> 
  {{ "controller", "Home" }, { "action", "Index" }, { "id", null });
routeTester.ForUrl("Home/Index")
  .AssertMapTo(new {controller = "Home", action = "Index"});
routeTester.ForUrl("Home/Index")
  .AssertMapTo("Home", "Index");
routeTester.ForUrl("Home/Index")
  .AssertMapTo<HomeController>(x => x.Index);

// alternate syntaxes for asserting Order/Details/3 routing:

routeTester.ForUrl("Order/Details/3")
  .AssertMapTo(new Dictionary<string, object> 
    {{"controller", "Order"}, {"action", "Details"}, {"id", 3});
routeTester.ForUrl("Order/Details/3")
  .AssertMapTo(
    new {controller = "Order", action = "Details", id = 3 });
routeTester.ForUrl("Order/Details/3")
  .AssertMapTo("Order", "Details", 3);
routeTester.ForUrl("Order/Details/3")
  .AssertMapTo<OrderController>(x => () => x.Details(3));
```

- - -

Happy Testing!