I have successfully setup up mocking to test asp.net forms authorization but I am seeing some unexpected behavior with the Role membership and the Authorize attribute. Specifically when the ChangePassword method is called as shown below I would expect that I would get a Unauthorized Access redirect to the Logon screen however I am able to step all the way through the ChangePassword method and receive a change password success. Can anyone help direct me in what I am doing wrong?
I have tested that in the ChangePassword method calling the IsUserInRole method does work as expected and I can redirect to the Logon screen in that condition but that seems burdensome to test for that condition in all my methods. Thanks in advance. I have also tried not assigning the user to a role (instead of returning false with mock) but the result is the same, change password succeeds.
[TestMethod]
public void TestProfile()
{
string testUserName = "userName", password = "password1", newPassword = "newPassword1";
var prov = new Mock<IMembershipProvider>();
prov.Setup(v => v.ValidateUser(testUserName, password)).Returns(true);
var user = new Mock<MembershipUser>();
var frmAuth = new Mock<IFormsAuthentication>();
user.Setup(v => v.ChangePassword(password, newPassword)).Returns(true);
prov.Setup(v => v.GetUser(testUserName, true)).Returns(user.Object);
AccountController ctrl = new AccountController(prov.Object, frmAuth.Object);
var ctrlCtx = new Mock<ControllerContext>();
ctrlCtx.SetupGet(x => x.HttpContext.User.Identity.Name).Returns(testUserName);
ctrlCtx.SetupGet(x => x.HttpContext.User.Identity.IsAuthenticated).Returns(true);
//with this line I would expect to see a redirect to unauhorized
ctrlCtx.Setup(x => x.HttpContext.User.IsInRole("RoleToTest")).Returns(false);
ctrl.ControllerContext = ctrlCtx.Object;
ctrl.Url = Moq.Mock.Of<IUrlHelper>(x => x.IsLocalUrl(It.IsAny<string>()) == true);
ChangePasswordModel changePass = new ChangePasswordModel() { NewPassword = newPassword, OldPassword = password, ConfirmPassword = password };
var result = ctrl.ChangePassword(changePass) as ViewResult;
string expectedViewName = "Logon";
Assert.AreEqual(result.ViewName, expectedViewName, true /* ignoreCase */,
string.Format("The expected view '{0}' was not returned. Did change password succeed?", expectedViewName));
}
[Authorize(Roles="RoleToTest")]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordModel model)
{
if (ModelState.IsValid)
{
// ChangePassword will throw an exception rather
// than return false in certain failure scenarios.
bool changePasswordSucceeded;
try
{
MembershipUser currentUser = membershipProvider.GetUser(User.Identity.Name, true /* userIsOnline */);
changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword);
}
catch (Exception e)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(e);
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return View("ChangePasswordSuccess");
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
// If we got this far, something failed, redisplay form
return View("ChangePassword", model);
}
Your expectation is wrong. Attributes such as
[Authorize]are just metadata baked into the assembly at compile-time. If there’s nothing to interpret them, well, nothing will happen at all at runtime.The thing is that the
[Authorize]attribute is used by the ASP.NET MVC request processing pipeline. In your unit test you are doing a simple call of the controller action. Nothing more. There’s no code to maker any sense of this attribute.So you don’t need to unit test that your controller action is redirecting to the LogOn page if the user is not authenticated. What you have to unit test is that your controller action is decorated with the Authorize attribute. The fact that this attribute will redirect to the Logon page when placed on a controller action is something that the ASP.NET MVC team already extensively unit tested during the development of the framework so you don’t need to repeat their job. Just trust them.
So here’s how a typical unit test could look like:
Alright, now you have unit tested that this controller action is only accessible to users belonging to the
RoleToTestrole.In your next unit test you assume that a user belongs to this role (by mocking the corresponding classes) and you assert that the body of the controller action executes as expected.