MVC view unit test visibility of element based on authentication - asp.net-mvc

I'm writing an MVC app.
If I have a piece of code:
#if (User.IsInRole("Administrator")) {
#Html.DropDownListFor(...)
}
So the dropdownlistfor is only visible or even there for administrators.
How do you unit test that this is happening?

This is what I would do:
Create a stub for the User object (of type IPrincipal)
Create a stub for the Request object (of type HttpRequestBase) with the stubbed User object
Inject the stubbed request object to your request context.
See example below (using Rhino mocks)
var CurrentUser = MockRepository.GenerateStub<IPrincipal>();
CurrentUser.Stub(u => u.IsInRole("Administrator")).Return(true);
var context = MockRepository.GenerateStub<HttpContextBase>();
var requestContext = new RequestContext(context, new RouteData());
var request = MockRepository.GenerateStub<HttpRequestBase>();
context.User = CurrentUser;
context.Stub(c => c.Request).Return(request);
controller = new YourController();
controller.ControllerContext = new ControllerContext(requestContext, controller);
var view = controller.DoAction() as ViewResult;
That would help you setup the controller and the current user with all required roles.
I personally wouldn't unit test the view as it is supposed to be thin and dump. Most of your stuff should happen in the controller/business layer.

If you want to unit test your views, I would suggest checking out the Razor Single File Generator. This will allow you to pre-compile your views, and to create unit tests as well.

Related

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

How do I unit test a custom ActionFilter in ASP.Net MVC

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.

Mocking a session in MVC 3

I am attempting to mock a controller's Session variable using Moq. What I keep running into is that the Session is visible from the test, but null inside the actual controller.
Test code:
[TestMethod]
public void SessionTest()
{
var controller = new BaseController();
var controllerContext = new Mock<ControllerContext>();
controllerContext.Setup(cc => cc.HttpContext.Session["user"]).Returns(new User());
controller.ControllerContext = controllerContext.Object;
User currentUser = controller.CurrentUser; //fails (throws NullReferenceException)
User currentUser = (User)controller.Session["test"]; //works
}
Controller code:
public User CurrentUser
{
get
{
return (User)Session["user"]; //HttpContext is null at this point
}
}
The code above follows the most common pattern I've seen (several SO postings, the MVC 2 in Action Ch. 7 example, etc.), but still doesn't work. Inside the controller, the ControllerContext is null, as is the HttpContext. What am I doing wrong?
It must have been a configuration issue caused by adding a unit test project to an already-existing project, because I just created a new MVC 3 project, checked the 'Create Unit Test Project', added all of my controllers, models, etc. from the old project to the new one, and it's working just fine.

Is it possible to copy/clone HttpContext of a web request

What's the easiest way to clone current request's HttpContext instance?
I'm developing an app in Asp.net MVC v1. I upgraded the regular PartialView capabilities to actually have sub-controllers that act very similar, but have their own context. When you use PartialViews you have to fill view data for the partial view in your main view's controller action. I created my own functionality that makes it possible to call controller actions from within a view. This way I get:
I don't have to provide sub-view's data in my main view's controller action
sub controller methods can manipulate data more encapsulated without any relation to other views/controllers
The problem is that each sub-controller request uses HttpContext. So when I set some HttpContext.Item in a sub-controller it actually populates HttpContext of the actual request.
That's why I want to clone HttpContext. I'm already using:
HttpContext subContext = new HttpContext(request, response);
// what happened to Session, User, Items etc. properties?
but this doesn't set anything else than request and response. But I would probably also need other properties and collections... Like Session, Items, User... etc.
While the "Not Possible" answer is correct, there is an alternative that is much cleaner than writing values into the current context and then rewriting back to its original state. The solution is to make a new HttpContext object entirely that is based on the URL of your choosing.
// A new request/response is constructed to using a new URL.
// The new response is using a StreamWriter with null stream as a backing stream
// which doesn't consume resources
using (var nullWriter = new StreamWriter(Stream.Null))
{
var newRequestUri = new Uri("http://www.somewhere.com/some-resource/");
var newRequest = new HttpRequest("", newRequestUri.ToString(), newRequestUri.Query);
var newResponse = new HttpResponse(nullWriter);
var newContext = new HttpContextWrapper(new HttpContext(newRequest, newResponse));
// Work with the new context here before it is disposed...
}
Reference: https://github.com/maartenba/MvcSiteMapProvider/issues/278#issuecomment-34905271
Not possible
I guess an actual deep cloning is not possible because of server session state. Cloning would also have to clone this value, which is web server specific internal resource that is intrinsically static and can not be cloned. In this case a web server would have multiple Session objects for instance.
Workaround
Anyway. The workaround was to set additional context values before instantiating sub-controller processing. After processing is finished I reverted values back to original. So I actually had context as it was before.
For ASP.Net Core/.Net 5 the following will work (based on the ASP.Net Core source code for SignalR, if you need more features just add them).
public static HttpContext Clone(this HttpContext httpContext, bool copyBody)
{
var existingRequestFeature = httpContext.Features.Get<IHttpRequestFeature>();
var requestHeaders = new Dictionary<string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase);
foreach (var header in existingRequestFeature.Headers)
{
requestHeaders[header.Key] = header.Value;
}
var requestFeature = new HttpRequestFeature
{
Protocol = existingRequestFeature.Protocol,
Method = existingRequestFeature.Method,
Scheme = existingRequestFeature.Scheme,
Path = existingRequestFeature.Path,
PathBase = existingRequestFeature.PathBase,
QueryString = existingRequestFeature.QueryString,
RawTarget = existingRequestFeature.RawTarget,
Headers = new HeaderDictionary(requestHeaders),
};
if(copyBody)
{
// We need to buffer first, otherwise the body won't be copied
// Won't work if the body stream was accessed already without calling EnableBuffering() first or without leaveOpen
httpContext.Request.EnableBuffering();
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
requestFeature.Body = existingRequestFeature.Body;
}
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(requestFeature);
// Unless we need the response we can ignore it...
features.Set<IHttpResponseFeature>(new HttpResponseFeature());
features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
var newContext = new DefaultHttpContext(features);
if (copyBody)
{
// Rewind for any future use...
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
}
// Can happen if the body was not copied
if(httpContext.Request.HasFormContentType && httpContext.Request.Form.Count != newContext.Request.Form.Count)
{
newContext.Request.Form = new Microsoft.AspNetCore.Http.FormCollection(httpContext.Request.Form.ToDictionary(f => f.Key, f => f.Value));
}
return newContext;
}
The ASP.NET MVC framework intentionally makes dependencies to abstract classes with all members virtual. That simply says - extensibility.
Controllers depend on HttpContextBase, not HttpContext. Perhaps you can make your sub-controllers depend on HttpContextBase too so you can wrap it.
Just my 2 cents.
I've used
<% Html.RenderAction("Action", "Controller"); %>
to great effect, allowing me to create completely isolated/escapsulated actions without resorting to complex code. This would seem to offer the same functionality without the same complexity.
The rendered views are standard partial views and the controller actions just like any other.

How to use Rhino Mocks to Mock an HttpContext.Application

I'm new to Mocking frameworks and have started using RhinoMocks to assist with my MVC App Unit Testing.
I'm using Scott Hanselmanns MVC Mock Helper to assist in mocking the HttpContext.
I've succesfully (after some time) mocked some of what I need but have come unstuck when it comes to the Application property of the HttpContext.
In my application I store an object in the Application and retrieve it within a Controller like:
SomeObj foo = (SomeObj)Application["fooKey"];
This gets created on Application_Start in my MVC App.
UPDATED FOLLOWING FIRST ANSWER (additional code for clarity)
Currently in the test setup I do:
HttpContextBase mockHttpBase = mocks.FakeHttpContext();
controllerToTest = new SomeController();
mocks.SetFakeControllerContext(controllerToTest);
HttpApplicationStateBase appState =
MockRepository.GenerateStub<HttpApplicationStateBase>();
Globals tmpAppGlobals =
new Globals();
mockHttpBase.Expect(ctx => ctx.Application).Return(appState);
mockHttpBase.Expect(ctx => ctx.Application[Globals.GlobalsKey]).
Return(tmpAppGlobals);
In my unit test setup I do:
Globals tmpAppGlobals = new Globals();
controllerToTest.ControllerContext.HttpContext.
Expect(ctx => ctx.Application[Globals.GlobalsKey]).
Return(tmpAppGlobals);
This call throws a NullReference Exception, for the Application object.
My question is two fold:
1) Is this the right approach or have I done something wrong from a design / architecture perspective?
2) Why doesn't this work?!
Thanks, in advance.
Without delving too deeply, this looks mostly correct.
The Application property is virtual on HttpContextBase, so you should be able to set up a return value for it from Rhino -- Assuming you're mocking HttpContextBase as Scott Hanselmanns post does.
Some possible causes, which are really just guesses from lack of information:
Did you set up returns for
controllerToTest.ControllerContext?
Did you set up a return for that
objects HttpContext property?
Did you set up a return for that
objects Application property?
The reason I ask is that typically when you do expectation setups, you already have references to the objects that will be called as part of your test, so you wouldn't do a property chain like you do with your controllerToTest.ControllerContext.HttpContext.
Expect() call.
Edit:
I think I see the problem, and I think it's with this part:
Expect(ctx => ctx.Application[Globals.GlobalsKey])
I think you're assuming that indexers work the same as properties, when they don't. What you really need to do is set up an expectation on your appState object to receive a call to the Item property, like this:
// setup expectations -- assumes some of the expectations and mocks
// the from original question
mockHttpBase.Expect(ctx => ctx.Application).Return(appState);
appState.Expect(ctx => ctx.Item(Globals.GlobalsKey)).Return(tmpAppGlobals);
// run the test
you could use the below for Moq. It took me awhile how to mock the HttpApplication, and the appState.Object is the return method duh!
public static HttpContextBase FakeHttpContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new FakeHttpSessionState();
var server = new Mock<HttpServerUtilityBase>();
var appState = new Mock<HttpApplicationStateBase>();
context.Setup(ctx => ctx.Request).Returns(request.Object);
context.Setup(ctx => ctx.Response).Returns(response.Object);
context.Setup(ctx => ctx.Session).Returns(session);
context.Setup(ctx => ctx.Server).Returns(server.Object);
context.Setup(ctx => ctx.Application).Returns(appState.Object);
//emulate session (HttpContext.Current.Session)
var contx = new HttpContext(new MyApp.NUnit.Tests.Fakes.FakeHttpWorkerRequest());
contx.Items["AspSession"] = CreateSession();
HttpContext.Current = contx;
return context.Object;
}

Resources