How do I unit test a controller action that uses ther Controller.User variable?
I have a controller action that automatically redirects to a new page if the user is already logged in (User.Identity.IsAuthenticated
). What is the best way to w开发者_JAVA百科rite a unit test for this scenario to ensure that the redirect takes places?
I've been using the following Mocks with Moq to allow setting up various conditions in my unit tests. First, the HttpContextBase mock:
public static Mock<HttpContextBase> GetHttpContextMock(bool isLoggedIn)
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var principal = AuthenticationAndAuthorization.GetPrincipleMock(isLoggedIn);
context.SetupGet(c => c.Request).Returns(request.Object);
context.SetupGet(c => c.Response).Returns(response.Object);
context.SetupGet(c => c.Session).Returns(session.Object);
context.SetupGet(c => c.Server).Returns(server.Object);
context.SetupGet(c => c.User).Returns(principal.Object);
return context;
}
Every property that might provide a useful Mock is set up in here. That way, if I need to add something like a referrer, I can just use:
Mock.Get(controller.Request).Setup(s => s.UrlReferrer).Returns(new Uri("http://blah.com/");
The "GetPrincipleMock" method is what sets up the user. It looks like this:
public static Mock<IPrincipal> GetPrincipleMock(bool isLoggedIn)
{
var mock = new Mock<IPrincipal>();
mock.SetupGet(i => i.Identity).Returns(GetIdentityMock(isLoggedIn).Object);
mock.Setup(i => i.IsInRole(It.IsAny<string>())).Returns(false);
return mock;
}
public static Mock<IIdentity> GetIdentityMock(bool isLoggedIn)
{
var mock = new Mock<IIdentity>();
mock.SetupGet(i => i.AuthenticationType).Returns(isLoggedIn ? "Mock Identity" : null);
mock.SetupGet(i => i.IsAuthenticated).Returns(isLoggedIn);
mock.SetupGet(i => i.Name).Returns(isLoggedIn ? "testuser" : null);
return mock;
}
Now, my controller setups in the tests look like this:
var controller = new ProductController();
var httpContext = GetHttpContextMock(true); //logged in, set to false to not be logged in
ControllerContext controllerContext = new ControllerContext(httpContext.Object, new RouteData(), controller);
controller.ControllerContext = controllerContext;
It's a little bit of verbose setup, but once you have everything in place, testing a variety of conditions becomes a lot easier.
That's not the simplest thing to do, but it can be done. The User property simply delegates to Controller.HttpContext.User. Both are non-virtual read-only properties, so you can't do anything about them. However, Controller.HttpContext delegates to ControllerBase.ControllerContext which is a writable property.
Therefore, you can assign a Test Double HttpContextBase to Controller.ControllerContext before exercising your System Under Test (SUT). Using Moq, it would look something like this:
var user = new GenericPrincipal(new GenericIdentity(string.Empty), null);
var httpCtxStub = new Mock<HttpContextBase>();
httpCtxStub.SetupGet(ctx => ctx.User).Returns(user);
var controllerCtx = new ControllerContext();
controllerCtx.HttpContext = httpCtxStub.Object;
sut.ControllerContext = controllerCtx;
Then invoke your action and verify that the return result is a RedirectResult.
This test utilizes the implicit knowledge that when you create a GenericIdentity with an empty name, it will return false for IsAuthenticated. You could consider making the test more explicit by using a Mock<IIdentity>
instead.
精彩评论