czf edited this page Apr 10, 2018 · 5 revisions

Web route usage

Assuming that the routes are declared along these lines:

routes = new RouteCollection();
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });

Then:

RouteAssert.HasRoute(routes, "/home/index");

This assertion will fail if the route table does not have a route to /home/index

var expectedRoute = new { controller = "Home", action = "Index", id = 42 };
RouteAssert.HasRoute(routes, "/home/Index/42", expectedRoute);

This assertion will fail if the route table does not have a route to the url /home/index/42, and will also fail if it is not mapped to the expected controller, action and other parameters, using a anon-typed object to specify the expectations, as per Phil Haack's article.

There are overloads of this method so if you don't like anonymous types, you can specify a controller name and action name as strings, or an IDictionary<string, string> to hold the controller name, action name and any other route parameters.

If you are using ASP MVC Areas, you can test that the right area is generated for the url, e.g.

RouteAssert.HasRoute(routes, "/FooArea", new { Area = "FooArea", Controller = "Home", Action = "Index" });

Other Web route tests

You can test that a route does not exist. This assertion will fail if a route can be found for the url /foo/bar/fish/spon.

RouteAssert.NoRoute(routes, "/foo/bar/fish/spon");

You can test url generation (e.g. the output of @Url.Action("Index", "Home") in a view) with

RouteAssert.GeneratesActionUrl(routes, "/", "Index", "Home");

If you are using ASP MVC Areas, you can test that the right area is generated for the url, e.g.

RouteAssert.GeneratesActionUrl(routes, "/SomeArea", new { action = "Index", controller = "Test", area = "SomeArea" });

API route usage

Assuming that the routes are declared along these lines:

config = new HttpConfiguration();
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional });;

Then:

RouteAssert.HasApiRoute(config, "/api/customer/1", HttpMethod.Get);

This assertion will fail if the config does not contain an Api route to the url /api/customer/1 which responds to the Http Get method.

var expectation = new { controller = "Customer", action= "get", id = "1" };
RouteAssert.HasApiRoute(config, "/api/customer/1", HttpMethod.Get, expectation);

You can assert on the particulars of the api route using the same kinds of expectations as with Mvc routes.

RouteAssert.ApiRouteDoesNotHaveMethod(config, "/api/customer/1", HttpMethod.Post);

This asserts that the Api route is valid, but that the controller there does not respond to the Http Post method.

```csharp
RouteAssert.NoApiRoute(config, "/pai/customer/1");

This asserts that the api route is not valid.

For more complete test examples, see the self-test code in MvcRouteTester.Test.

API

All methods are on the RouteAssert class.

Web routes

public static void HasRoute(RouteCollection routes, string url)

Test that a web route matching the url exists.


public static void HasRoute(RouteCollection routes, string url, object expectations)
public static void HasRoute(RouteCollection routes, string url, string controller, string action)
public static void HasRoute(RouteCollection routes, string url, IDictionary<string, string> expectedProps)

Test that a web route matching the url exists, and that it meets expectations. The expectations can be given in different ways - as an anon-typed object, as an IDictionary of names and values, or to just check the controller and action method names.

public static void HasRoute(RouteCollection routes, string url, string body, IDictionary<string, string> expectedProps)

You can also specify a request body. This can be used for testing that post body data is mapped to route parameters. The body must be a string containing application/x-www-form-urlencoded values.

Json post body is also supported. You can specify the body format with:

public static void HasRoute(RouteCollection routes, string url, string body, BodyFormat bodyFormat, IDictionary<string, string> expectedProps)
public static void HasRoute(RouteCollection routes, string url, HttpMethod httpMethod, string body, BodyFormat bodyFormat, IDictionary<string, string> expectedProps)

Where the body format is one of BodyFormat.FormUrl or BodyFormat.Json.


public static void NoRoute(RouteCollection routes, string url)

Test that a web route matching the url does not exist.


public static void IsIgnoredRoute(RouteCollection routes, string url)

Test that the url is explicitly ignored, e.g. it matches a route added with routes.IgnoreRoute


public static void IsNotIgnoredRoute(RouteCollection routes, string url)

Test that the url is not explicitly ignored, i.e. it matches a route which is not an ignored route.


public static void GeneratesActionUrl(RouteCollection routes, string expectedUrl, object fromProps, HttpMethod httpMethod = null, string appPath = "/", string requestBody = null)
public static void GeneratesActionUrl(RouteCollection routes, string expectedUrl, IDictionary<string, string> fromProps, string appPath = "/")
public static void GeneratesActionUrl(RouteCollection routes, string expectedUrl, string action, string controller, HttpMethod httpMethod = null, string appPath = null, string requestBody = null)
public static void GeneratesActionUrl(RouteCollection routes, HttpMethod httpMethod, string expectedUrl, IDictionary<string, string> fromProps, string appPath = null, string requestBody = null)
public static void GeneratesActionUrl(RouteCollection routes, string expectedUrl, string action, HttpMethod httpMethod = null, string appPath = null, string requestBody = null)

Test that the expected url is generated by feeding data into Url.Action.


Api routes

Api routes work a bit differently to web routes. Once an entry in the route table has been matched, the controller must be found to see if it has a method to respond to the given Http method, so there are two different levels of matching.


public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod)

Test that an Api route matching the url exists, and that the controller can respond to the specified Http method.


public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod, object expectations)
public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod, string controller, string action)
public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod, IDictionary<string, string> expectedProps)

Test that an api route matching the url exists, and that the controller can respond to the specified Http method and meets expectations. The expectations can be given in different ways - as an anon-typed object, as an IDictionary of names and values, or to just check the controller and action method names.

public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod, string body, BodyFormat bodyFormat, object expectations)
public static void HasApiRoute(HttpConfiguration config, string url, HttpMethod httpMethod, string body, BodyFormat bodyFormat,  IDictionary<string, string> expectedProps)

You can also specify a request body on an api request. This can be used for testing that post body data is mapped to route parameters. As with Web routes, the body format is one of BodyFormat.FormUrl or BodyFormat.Json. for application/x-www-form-urlencoded or Json values.


public static void NoApiRoute(HttpConfiguration config, string url)

Test that an Api route for the url does not exist. This means that it either does not match any pattern in the route table, or it does match a route table entry, but a matching controller cannot be found.


public static void ApiRouteDoesNotHaveMethod(HttpConfiguration config, string url, HttpMethod httpMethod)

Asserts that an API route for the url exists but does not have the specified Http method.


public static void ApiRouteDoesNotHaveMethod(HttpConfiguration config, string url, Type controllerType, HttpMethod httpMethod)

Asserts that the API route for the url exists to the specified controller, but does not have the specified Http method.


public static void ApiRouteMatches(HttpConfiguration config, string url)

Test that an api route matching the url exists. This is a weaker test as it does not attempt to locate the controller, just tests that the url matches a route's pattern.


public static void NoApiRouteMatches(HttpConfiguration config, string url)

Test that an api route matching the url does not exist. This means that it does not match the pattern of any entry in the route table.

Fluent extensions

The fluent interface is a different way to use the same route testing engine. Many people prefer it as it is more strongly typed and may be more readable. It has the advantage that expressing parameters as type names and method calls rather than strings means that the test cannot be out of sync with your controller code. e.g. if you change a controller type name, the test will fail to compile, or if you use refactoring tools it will also be changed to match.

The fluent interface allows you to write code like:

routes.ShouldMap("/home/index/32").To<HomeController>(x => x.Index(32));
routes.ShouldMap("fred.axd").ToIgnoredRoute();
routes.ShouldMap("/foo/bar/fish/spon").ToNoRoute();
routes.ShouldMap("/home/index/32").WithFormUrlBody("Name=Fred&Id=12").To<HomeController>(x => x.Index(32));
routes.ShouldMap("/").From<HomeController>(x => x.Index());

These use RouteAssert.HasRoute, RouteAssert.IsIgnoredRoute, RouteAssert.NoRoute and RouteAssert.GeneratesActionUrl respectively. the controller name, action name and action params are read from the types and expression given.

The post body can be given as a string containing form-url encoded data using WithFormUrlBody or containing Json data with WithJsonBody.

And for Api routes:

config.ShouldMap("/api/customer/32").To<CustomerController>(HttpMethod.Get, x => x.Get(32));
config.ShouldMap("/api/customer/32").WithFormUrlBody("Name=Fred&Id=12").To<CustomerController>(HttpMethod.Post, x => x.Index(32));

config.ShouldMap("/api/customer/32").ToNoMethod<CustomerController>(HttpMethod.Post);
config.ShouldMap("/pai/customer/32").ToNoRoute();

These use RouteAssert.HasApiRoute, RouteAssert.ApiRouteDoesNotHaveMethod, RouteAssert.NoApiRoute respectively.

Areas

If you are using MVC areas, then as long as you use the standard layout of namespaces, the area name will be extracted from your controller type name and tested against the area chosen by the route. e.g. if your controller's fully qualified type name is MyWebApp.Areas.Blog.CommentController then the expected area name will be "Blog".

Testing handlers

The fluent extensions have methods to test if a custom http handler is used by a route:

public void WithHandler<THandler>() where THandler : DelegatingHandler

Usage:

config.ShouldMap("/api/customer/32").WithHandler<CustomerHandle>();

or

config.ShouldMap("/api/customer/32").To<CustomerController>(HttpMethod.Get, x => x.Get(32)).WithHandler<CustomerHandle>();

You can assert that a particular handler is not being used with

public void WithoutHandler<THandler>() where THandler : DelegatingHandler

Or assert that no handler is used:

public void WithoutHandler()

Usage:

config.ShouldMap("/api/customer/32").WithoutHandler<WrongHandler>();
config.ShouldMap("/api/customer/32").To<CustomerController>(HttpMethod.Get, x => x.Get(32)).WithoutHandler();

Model binding

Model binding is the MVC framework's mapping from the URL to the controller method's parameters. This is one of the things that this library aims to test. Binding can be to simple types (int, string, bool etc.) or to a Model class. Suppose you have a controller

public class FirstController: Controller
{
    // simple model binding
    public ActionResult Index(int id, string name)
    {
        ...
    }
}

But for another controller you decide to bind incoming parameters into an model class, e.g.

public class InputModel
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SecondController : Controller
{
    // binding to an object of type 'InputModel'
    public ActionResult Index(InputModel data)
    {
        ...
    }
}

You can test both of these. With the expectation syntax, both controllers should be tested in the same way, e.g.

var firstExpected = new { controller = "First", action = "Index", id = "1", name = "fred" };
RouteAssert.HasRoute(routes, "/first/index/1/fred", firstExpected);

var secondExpected = new { controller = "Second", action = "Index", id = "1", name = "fred" };
RouteAssert.HasRoute(routes, "/second/index/1/fred", secondExpected);

However, with the fluent syntax the values will be read off the data in the lambda, so the two look tests slightly different to each other:

routes.ShouldMap("/first/index/1/fred").To<FirstController>(x => x.Index(1, "Fred"));
routes.ShouldMap("/second/index/1/fred").To<SecondController>(x => x.Index(new InputModel { Id = 1, Name = "fred" }));

The situation is the same for testing API Controllers.

Ignoring properties using attributes

Sometimes the model object has some properties that should not be tested as they are not used by the model binder. These properties are usually marked with some attribute, e.g. [JsonIgnore]. The MvcRouteTester has a list of attributes that mark properties as ignored. This list is initially empty, but you can configure it by adding types one at a time or in a list:

RouteAssert.AddIgnoreAttribute(Type type)
RouteAssert.AddIgnoreAttributes(IEnumerable<Type> types)

And reset the list by clearing it:

RouteAssert.ClearIgnoreAttributes()
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.