I have seen some very helpful posts about testing Microsoft's routing. One in particular www.strathweb.com/2012/08/testing-routes-in-asp-net-web-api/ seems to deal just with WebApi. Though similiar they are not the same. If I have an MVC application how do I see the method that will be invoked for a given URL. It seems to boils down to creating a 'Request' that can be passed to the constructor of HttpControllerContext and obtaining a reference to the 'current' config (like HttpConfiguration) in testing. Ideas?
Thank you.
Testing Incoming URL
If you need to test routes, you need to mock three classes from the MVC Framework: HttpRequestBase, HttpContextBase and HttpResponseBase(only for outgoing URL´s)
private HttpContextBase CreateHttpContext(string targetUrl = null, string httpMethod = "GET")
{
// create mock request
Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
// url you want to test through the property
mockRequest.Setup(m => m.AppRelativeCurrentExecutionFilePath).Returns(targetUrl);
mockRequest.Setup(m => m.HttpMethod).Returns(httpMethod);
// create mock response
Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
mockResponse.Setup(m => m.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(s => s);
// create the mock context, using the request and response
Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
mockContext.Setup(m => m.Request).Returns(mockRequest.Object);
mockContext.Setup(m => m.Response).Returns(mockResponse.Object);
// return the mock context object
return mockContext.Object;
}
then you need an additional helper method that let´s you specify the URL to test and the expected segment variables and an object for additional variables.
private void TestRouteMatch(string url, string controller, string action,
object routeProperties = null, string httpMethod = "GET")
{
// arrange
RouteCollection routes = new RouteCollection();
// loading the defined routes about the Route-Config
RouteConfig.RegisterRoutes(routes);
RouteData result = routes.GetRouteData(CreateHttpContext(url, httpMethod));
// assert
Assert.IsNotNull(result);
// here you can check your properties (controller, action, routeProperties) with the result
Assert.IsTrue(.....);
}
You don´t need to define your routes in the test methodes, because they were load directly using the RegisterRoutes method in the RouteConfig class.
The mechanism by wich inbound URL matching works.
GetRouteData(HttpContextBase httpContext)
referencesource.microsoft
The framework calls this method for each route table entry, until one of thems returns a non-null value.
You have to call the helper method as example in this way
[TestMethod]
public void TestIncomingRoutes() {
// check for the URL that is hoped for
TestRouteMatch("~/Home/Index", "Home", "Index");
}
the method check the URL you expecting as in the example above, call the Index action in the Home controller. You must prefix the URL with tilde (~) this is they way how the ASP.NET Framework presents the URL to the routing system.
In reference to the book Pro ASP.NET MVC 5 by Adam Freeman i can recommand it to every ASP.NET MVC developer!
Related
I'm testing the Account/Loggon action using the built-in testing tool of Visual Studio 2010 and the class library from this article to create a fake controller context.
When I run the test method, this code line:
FormsAuthentication.SetAuthCookie(username, false);
throws an exception: Object reference not set to an instance of an object
To test the loggon action, I think I should create a controller with a fake controller context that has a cookie collection. Here is my testing code block:
AccountController controller = new AccountController();
var cookies = new HttpCookieCollection();
controller.ControllerContext = new FakeControllerContext(controller, cookies);
ActionResult result = controller.RemoteLogOn(username, password);
I'm not sure if this is the right way, but this is what we do, and it works.
Instead of directly using FormsAuthentication.SetAuthCookie, abstract it into an interface, e.g IFormsAuthenticationService, and implement as per regular.
Accept that in your MVC controllers where required, e.g:
public AccountController(IFormsAuthenticationService formsAuthenticationService)
{
_formsAuthenticationService = formsAuthenticationService; // should use DI here
}
public ActionResult LogOn(string username, string pw)
{
if (yourLogicWhichChecksPw)
_formsAuthenticationService.SetAuthCookie(username, false);
return RedirectToAction("Index");
}
Then in your unit-test, use something like Moq to fake out the interface.
var username = "blah";
var pw = "blah";
var fakesFormsAuth = new Mock<IFormsAuthenticationService>();
fakeFormsAuth.Verify(x => x.SetAuthCookie(username, false), Times.AtLeastOnce());
var controller = new AccountController(fakedFormsAuth.Object);
controller.LogOn(username, pw);
The reason for mocking this is because there is absolutely no need to unit-test Forms Authentication. It's a built-in, well tested and stable part of the ASP.NET framework. That's why we mock things where we don't care about the underlying implementation, instead we only test that certain conditions were met (it was called, exception was thrown, some variable was set, etc).
Test your own code, not the mechanics of .NET.
As for Stephen Walther's article, that's more for faking the RequestContext when certain code your testing expects data in the Request. Such as the User.Identity, Request.IsAuthenticated, Form variables, etc. That's where you need to fake the context, such as the following code:
public ActionResult Save(SomeModel)
{
var user = Request.User.Identity; // this will be null, unless you fake the context.
}
So I'm creating a custom ActionFilter that's based mostly on this project http://www.codeproject.com/KB/aspnet/aspnet_mvc_restapi.aspx.
I want a custom action filter that uses the http accept headers to return either JSON or Xml. A typical controller action will look like this:
[AcceptVerbs(HttpVerbs.Get)]
[AcceptTypesAttribute(HttpContentTypes.Json, HttpContentTypes.Xml)]
public ActionResult Index()
{
var articles = Service.GetRecentArticles();
return View(articles);
}
The custom filter overrides the OnActionExecuted and will serialize the object (in this example articles) as either JSON or Xml.
My question is: how do I test this?
What tests do I write? I'm a TDD novice and am not 100% sure what I should be testing and what not to test. I came up with AcceptsTypeFilterJson_RequestHeaderAcceptsJson_ReturnsJson(), AcceptsTypeFilterXml_RequestHeaderAcceptsXml_ReturnsXml() and AcceptsTypeFilter_AcceptsHeaderMismatch_ReturnsError406().
How do I test an ActionFilter in MVC that is testing the Http Accept Headers?
Thanks.
You just need to test the filter itself. Just create an instance and call the OnActionExecuted() method with test data then check the result. It helps to pull the code apart as much as possible. Most of the heavy lifting is done inside the CsvResult class which can be tested individually. You don't need to test the filter on an actual controller. Making that work is the MVC framework's responsibility.
public void AcceptsTypeFilterJson_RequestHeaderAcceptsJson_ReturnsJson()
{
var context = new ActionExecutedContext();
context.HttpContext = // mock an http context and set the accept-type. I don't know how to do this, but there are many questions about it.
context.Result = new ViewResult(...); // What your controller would return
var filter = new AcceptTypesAttribute(HttpContentTypes.Json);
filter.OnActionExecuted(context);
Assert.True(context.Result is JsonResult);
}
I just stumbled upon this blog post which seems the right way to me. He uses Moq.
What this chap is doing is mocking the HTTPContext, but also we need to set up a ContentType in the request:
// Mock out the context to run the action filter.
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.ContentType).Returns("application/json");
var httpContext = new Mock<HttpContextBase>();
httpContext.SetupGet(c => c.Request).Returns(request.Object);
var routeData = new RouteData(); //
routeData.Values.Add("employeeId", "123");
var actionExecutedContext = new Mock<ActionExecutedContext>();
actionExecutedContext.SetupGet(r => r.RouteData).Returns(routeData);
actionExecutedContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);
var filter = new EmployeeGroupRestrictedActionFilterAttribute();
filter.OnActionExecuted(actionExecutedContext.Object);
Note - I have not tested this myself.
So this really applies to several different classes like HttpContext, ConfigurationManager, etc. There are several different ways to handle this, and I have always used wrapper classes to handle this stuff, but I wanted to see what the most common community practice is...
Wrapper classes - e.g. I would have an HttpContextService which I pass in via constructor that exposes all the same functionality via flat method calls.
Wrapper classes (part 2) - e.g. I would have SPECIFIC service classes, like MembershipService, which LEVERAGES HttpContext behind the scenes. Functions the same as 1, but the naming scheme / usage pattern is a little different as you are exposing specific functions through specific services instead of one monolithic wrapper. The downside is that the number of service classes that need to be injected goes up, but you get some modularity for when you don't need all the features of the monolithic wrapper.
ActionFilters and parameters - Use an ActionFilter to automatically pass in certain values needed on a per function basis. MVC only, and limits you to the controller methods, whereas 1 and 2 could be used generically throughout the project, or even in conjunction with this option.
Directly mocking HttpContextBase and setting ControllerContext - There are several mocking framework extension methods out there to help with this, but essentially requires you to directly set things as needed. Doesn't require abstractions, which is nice, and can be reused across non-controller tests as well. Still leaves open the question for ConfigurationManager and other static method calls though, so you could end up with injecting that ANYWAY, but leaving HttpContext to be accessed in this other way.
Right now I am kind of doing number 1, so I have an HttpContextService and a ConfigurationManagerService, etc. which I then inject, though I'm leaning toward 2 in the future. 3 seems to be a little too messy for my tastes, but I can see the appeal for controller methods, and the need for a completely separate solution for other areas of code that also use these static classes makes that one kind of poor for me... 4 is still interesting to me as it seems the most "natural" in terms of basic functionality and leverages the built-in methodologies of MVC.
So what is the prevailing Best Practice here? What are people seeing and using in the wild?
There are already "wrapper" classes for HttpContext, HttpRequest, HttpResponse, etc. The MVC framework uses these and you can supply mocks of them to the Controller via the controller context. You don't need to mock the controller context as you can simply create one with the appropriate values. The only thing I've found difficult to mock are the helpers, UrlHelper and HtmlHelper. Those have some relatively deep dependencies. You can fake them in a somewhat reasonable way, UrlHelper shown below.
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
var routeData = new RoutedData();
var controller = new HomeController();
controller.ControllerContext = new ControllerContext( httpContext, routeData, controller );
controller.Url = UrlHelperFactory.CreateUrlHelper( httpContext, routeDate );
where
public static class UrlHelperFactory
{
public static UrlHelper CreateUrlHelper( HttpContextBase httpContext, RouteData routeData )
{
return CreateUrlHelper( httpContext, routeData, "/" );
}
public static UrlHelper CreateUrlHelper( HttpContextBase httpContext, RouteData routeData, string url )
{
string urlString = string.Format( "http://localhost/{0}/{1}/{2}", routeData.Values["controller"], routeData.Values["action"], routeData.Values["id"] ).TrimEnd( '/' );
var uri = new Uri( urlString );
if (httpContext.Request == null)
{
httpContext.Stub( c => c.Request ).Return( MockRepository.GenerateStub<HttpRequestBase>() ).Repeat.Any();
}
httpContext.Request.Stub( r => r.Url ).Return( uri ).Repeat.Any();
httpContext.Request.Stub( r => r.ApplicationPath ).Return( "/" ).Repeat.Any();
if (httpContext.Response == null)
{
httpContext.Stub( c => c.Response ).Return( MockRepository.GenerateStub<HttpResponseBase>() ).Repeat.Any();
}
if (url != "/")
{
url = url.TrimEnd( '/' );
}
httpContext.Response.Stub( r => r.ApplyAppPathModifier( Arg<string>.Is.Anything ) ).Return( url ).Repeat.Any();
return new UrlHelper( CreateRequestContext( httpContext, routeData ), GetRoutes() );
}
public static RequestContext CreateRequestContext( HttpContextBase httpContext, RouteData routeData )
{
return new RequestContext( httpContext, routeData );
}
// repeat your route definitions here!!!
public static RouteCollection GetRoutes()
{
RouteCollection routes = new RouteCollection();
routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "home", action = "index", id = "" } // Parameter defaults
);
return routes;
}
}
We're currently running on IIS6, but hoping to move to IIS 7 soon.
We're moving an existing web forms site over to ASP.Net MVC. We have quite a few legacy pages which we need to redirect to the new controllers. I came across this article which looked interesting:
http://blog.eworldui.net/post/2008/04/ASPNET-MVC---Legacy-Url-Routing.aspx
So I guess I could either write my own route handler, or do my redirect in the controller. The latter smells slightly.
However, I'm not quite sure how to handle the query string values from the legacy urls which ideally I need to pass to my controller's Show() method. For example:
Legacy URL:
/Artists/ViewArtist.aspx?Id=4589
I want this to map to:
ArtistsController Show action
Actually my Show action takes the artist name, so I do want the user to be redirected from the Legacy URL to /artists/Madonna
Thanks!
depending on the article you mentioned, these are the steps to accomplish this:
1-Your LegacyHandler must extract the routes values from the query string(in this case it is the artist's id)
here is the code to do that:
public class LegacyHandler:MvcHandler
{
private RequestContext requestContext;
public LegacyHandler(RequestContext requestContext) : base(requestContext)
{
this.requestContext = requestContext;
}
protected override void ProcessRequest(HttpContextBase httpContext)
{
string redirectActionName = ((LegacyRoute) RequestContext.RouteData.Route).RedirectActionName;
var queryString = requestContext.HttpContext.Request.QueryString;
foreach (var key in queryString.AllKeys)
{
requestContext.RouteData.Values.Add(key, queryString[key]);
}
VirtualPathData path = RouteTable.Routes.GetVirtualPath(requestContext, redirectActionName,
requestContext.RouteData.Values);
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.AppendHeader("Location", path.VirtualPath);
}
}
2- you have to add these two routes to the RouteTable where you have an ArtistController with ViewArtist action that accept an id parameter of int type
routes.Add("Legacy", new LegacyRoute("Artists/ViewArtist.aspx", "Artist", new LegacyRouteHandler()));
routes.MapRoute("Artist", "Artist/ViewArtist/{id}", new
{
controller = "Artist",
action = "ViewArtist",
});
Now you can navigate to a url like : /Artists/ViewArtist.aspx?id=123
and you will be redirected to : /Artist/ViewArtist/123
I was struggling a bit with this until I got my head around it. It was a lot easier to do this in a Controller like Perhentian did then directly in the route config, at least in my situation since our new URLs don't have id in them. The reason is that in the Controller I had access to all my repositories and domain objects. To help others this is what I did:
routes.MapRoute(null,
"product_list.aspx", // Matches legacy product_list.aspx
new { controller = "Products", action = "Legacy" }
);
public ActionResult Legacy(int catid)
{
MenuItem menuItem = menu.GetMenuItem(catid);
return RedirectPermanent(menuItem.Path);
}
menu is an object where I've stored information related to menu entries, like the Path which is the URL for the menu entry.
This redirects from for instance
/product_list.aspx?catid=50
to
/pc-tillbehor/kylning-flaktar/flaktar/170-mm
Note that RedirectPermanent is MVC3+. If you're using an older version you need to create the 301 manually.
I am using an authentication attribute on some of my actions in an asp.net mvc page to refer people to a login screen if they have not authenticated. My problem is returning them to the referring page after they have logged in. I was just keeping track of the referring action and referring controller but that becomes problematic when I also need to keep track of some parameters. Is there some nifty built in trick about which I don't know?
In case you're using FormsAuthentication, when ASP.NET redirects a user to the login page, the URL looks something like this:
http://www.mysite.com/Login?ReturnUrl=/Something
The login form's action attribute should have the same ReturnUrl parameter (either as hidden input or as part of Url) so that FormsAuthentication can pick it up and redirect, e.g.
<form action="Login?ReturnUrl=<%=Html.AttributeEncode(Request.QueryString["ReturnUrl"]) %>"></form>
or
<form><input type="hidden" name="ReturnUrl" id="ReturnUrl" value="<%=Html.AttributeEncode(Request.QueryString["ReturnUrl"])"%> /></form>
What I did to achieve that result might be overdoing it, and I'd like to see some other methods as well. However, here's my code.
Please note it's using Moq to mock a context... And, I haven't done anything with the querystring yet (my routes don't contain any querystrings).
var urlReferrer = Request.UrlReferrer;
if (urlReferrer != null)
{
var url = "~" + Server.UrlDecode(urlReferrer.PathAndQuery);
// get routecollection
var routeCollection = new RouteCollection();
GlobalApplication.RegisterRoutes(routeCollection);
// mcok context
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
context.Expect(ctx => ctx.Request).Returns(request.Object);
// mock request
// TODO: convert querystring to namevaluecollection
// now it's just stripped
if (url.IndexOf('?') > 0)
{
url = url.Substring(0, url.IndexOf('?'));
}
var mock = Mock.Get(context.Object.Request);
// TODO: insert namevaluecollection of querystring
mock.Expect(req => req.QueryString).Returns(new NameValueCollection());
mock.Expect(req => req.AppRelativeCurrentExecutionFilePath).Returns(url);
mock.Expect(req => req.PathInfo).Returns(string.Empty);
// get routedata with mocked context
var routeData = routeCollection.GetRouteData(context.Object);
var values = routeData.Values;
return RedirectToAction(routeData.Values["action"].ToString(), values);
}
As I said, it's maybe a bit overcomplicated :)
You should always ensure that the referring URL is within your domain and a plausible string that they could be coming from. Otherwise this has the potential of being used with flash or other client side technologies to do things like response splitting or other attacks, known and unknown.
The HTTP referer is user input, and it should be validated like any other.