MVC3 HttpContext unit testing / mocking options - asp.net-mvc

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;
}
}

Related

Test URL to MVC Controller method

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!

How to inject DbContext in a custom localization provider in ASP.NET Core?

As explained in the asp.net core docs you can configure a custom provider for request localization. As stated in the docs:
Suppose you want to let your customers store their language and culture in your databases. You could write a provider to look up these values for the user.
For that the following code snippet is provided in the docs and also in the github sample Localization.StarterWeb:
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr")
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
{
// My custom request culture logic
// DbContext needed here <--
return new ProviderCultureResult("en");
}));});
Can anybody explain me how to inject a DbContext to load the user specific language from DB in the above function?
Well, you can't inject it via constructor because you need to instantiate it during ConfigureServices method and the container isn't available at this point.
Instead you can resolve via HttpContext.
public class CustomRequestCultureProvider : RequestCultureProvider
{
// Note we don't inject any dependencies into it, so we can safely
// instantiate in ConfigureServices method
public CustomRequestCultureProvider() { }
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
var dbContext = httpContext.RequestServices
.GetService<AppDbContext>();
}
}
Be aware though that this may be less than optimal, as you'll have calls to database on every request, so maybe it's worth to abstract this further and use an caching strategy depending on what exactly you want to do with the DbContext.
Usually one should avoid database calls in culture providers, filters etc. for performance reasons
Update:
There is a generic version of GetService<T>, but you need to import the namespace via using Microsoft.Extensions.DependencyInjection;.

How to mock Request.IsAjaxRequest() to true using FakeItEasy?

Following is the code snippet for which I want to write unit tests:
[HttpGet]
public ActionResult Edit(string id)
{
if (Request.IsAjaxRequest())
{
EditModel model = new EditModel();
.....
}
return View();
}
I want to write unit tests for this action where I can fake the result of Request.IsAjaxRequest() to true so that I can write tests for rest of the code of the action.
I have tried following but it doesn't work. _request.Headers is always empty, and Request.IsAjaxRequest() is always returning false:
[Fact]
public void Get_Edit_AjaxRequest_ExpectedActionCalled()
{
HttpRequestBase _request = A.Fake<HttpRequestBase>();
_request.Headers.Add("X-Requested-With", "XMLHttpRequest");
_controller.ControllerContext = A.Fake<ControllerContext>();
_controller.ControllerContext.HttpContext = _request;
A.CallTo(() => _controller.Request).Returns(_request);
var result = _controller.Edit(1) as RedirectToRouteResult;
}
I always get Request.IsAjaxRequest() as false. Any help on this much appreciated. Thanks
I managed to muddle past the compilation errors and use some information from Chapter 10 of FakeItEasy Succinctly, which is all about ASP.NET MVC.
Generally speaking, the ASP.NET MVC classes are not designed in a way to make them easily fakeable, but I have a test setup that causes IsAjaxRequest to return true. The two main hurdles were getting the controller to use the request object and to make sure that the request object was returning the headers we wanted.
The first part was not hard, but the second required us to have the request object use a concrete NameValueCollection. The faked one that it had been providing by default was not useful, because the right properties weren't virtual. Fortunately, using a real NameValueCollection did the trick.
Try this:
[Fact]
public void Get_Edit_AjaxRequest_ExpectedActionCalled_Blair()
{
HttpRequestBase _request = A.Fake<HttpRequestBase>();
// NameValueCollection is effectively unfakeable due to non-virtual properties,
// but a real one works just fine, so make sure the headers use one of those.
A.CallTo(() => _request.Headers).Returns(new NameValueCollection());
_request.Headers["X-Requested-With"] = "XMLHttpRequest";
var httpContext = A.Fake<HttpContextBase>();
A.CallTo(() => httpContext.Request).Returns(_request);
_controller.ControllerContext = new ControllerContext(
new RequestContext(httpContext, new RouteData()),
_controller);
var result = _controller.Edit(1) as RedirectToRouteResult;
}
Be warned that there will be lots of pitfalls like this in the MVC framework, and continuing to fake them may continue to be frustrating. You may find a more sustainable approach is to extract as much of your logic as is feasible out into plain old testable business classes that don't rely on the MVC framework.

Testing Account/Logon Action

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.
}

Generate URL using Subdomain routing in ASP .NET MVC 3

There are a lot of material written about Subdomain routing in ASP.NET MVC. Some of them use Areas as target for subdomains other use another Controllers.
There are some of them:
Subdomains for a single application with ASP.NET MVC
Asp.Net MVC 2 Routing SubDomains to Areas
MVC 3 Subdomain Routing
MVC-Subdomain-Routing on Github
They do all explain how to accept and route requests with subdomain.
But:
None of them explains how to generate URLs with subdomain. I.e. I tried #Html.RouteLink("link to SubDomain", "SubdomainRouteName") but what it ignores subdomain and generates url without it
How to deal with the same names of controllers from different areas. All those solutions (they use namespaces for these purpose) throw exception that exist several controllers and suggest using namespaces :)
Purpose:
create mobile version of site using subdomain
I've wrote a post on how I use subdomain routing in my application. The source code is available on the post, but I'll try to explain how I did my custom RouteLink method.
The helper method uses the RouteTable class to get the Route object based on the current Url and cast it to a SubdomainRoute object.
In my case all routes are defined using the SubdomainRoute and everytime I need to add a link to some other page I use my custom RouteLink helper, this is why I consider this cast safe. With the SubdomainRoute variable available I'm able to get the subdomain name and then build the Url using the UriBuilder class.
This is the code I'm currently using.
public static IHtmlString AdvRouteLink(this HtmlHelper htmlHelper, string linkText, string routeName, object routeValues, object htmlAttributes)
{
RouteValueDictionary routeValueDict = new RouteValueDictionary(routeValues);
var request = htmlHelper.ViewContext.RequestContext.HttpContext.Request;
string host = request.IsLocal ? request.Headers["Host"] : request.Url.Host;
if (host.IndexOf(":") >= 0)
host = host.Substring(0, host.IndexOf(":"));
string url = UrlHelper.GenerateUrl(routeName, null, null, routeValueDict, RouteTable.Routes, htmlHelper.ViewContext.RequestContext, false);
var virtualPathData = RouteTable.Routes.GetVirtualPathForArea(htmlHelper.ViewContext.RequestContext, routeName, routeValueDict);
var route = virtualPathData.Route as SubdomainRoute;
string actualSubdomain = SubdomainRoute.GetSubdomain(host);
if (!string.IsNullOrEmpty(actualSubdomain))
host = host.Substring(host.IndexOf(".") + 1);
if (!string.IsNullOrEmpty(route.Subdomain))
host = string.Concat(route.Subdomain, ".", host);
else
host = host.Substring(host.IndexOf(".") + 1);
UriBuilder builder = new UriBuilder(request.Url.Scheme, host, 80, url);
if (request.IsLocal)
builder.Port = request.Url.Port;
url = builder.Uri.ToString();
return htmlHelper.Link(linkText, url, htmlAttributes);
}
private static IHtmlString Link(this HtmlHelper htmlHelper, string text, string url, object htmlAttributes)
{
TagBuilder tag = new TagBuilder("a");
tag.Attributes.Add("href", url);
tag.InnerHtml = text;
tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}

Resources