I'm writing a unit test using Moq and Microsoft's testing tools for an ASP.NET MVC 5 app that uses ASP.NET Identity. The test needs to simulate a logon failure in the Login
POST action, such that it produces SignInStatus.Failure
from the SignInManager.PasswordSignInAsync
method of my AccountController
:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login([Bind(Include = "Email,Password,RememberMe")]
LoginViewModel model, string returnUrl)
{
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password,
model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return Redirect(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Here are the helper members/methods on my [TestClass]
:
private Mock<HttpContextBase> _httpContextMock;
private Mock<HttpRequestBase> _httpRequestMock;
private Mock<HttpResponseBase> _httpResponseMock;
private MyApp.Controllers.AccountController _controller;
private Mock<ControllerContext> _controllerContextMock;
[TestInitialize]
public void SetupTests()
{
// Set up Moq
_httpContextMock = new Mock<HttpContextBase>();
_httpRequestMock = new Mock<HttpRequestBase>();
_httpResponseMock = new Mock<HttpResponseBase>();
_controller = new AccountController();
_controllerContextMock = new Mock<ControllerContext>();
}
private void SetContexts(IPrincipal user)
{
_httpContextMock.Setup(x => x.Items).Returns(new Dictionary<string, object>());
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
//this adds aspnet session
_httpContextMock.Object.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
_httpContextMock.Setup(x => x.User).Returns(user);
// https://stackoverflow.com/questions/674458/asp-net-mvc-unit-testing-controllers-that-use-urlhelper
_httpRequestMock.SetupGet(x => x.ApplicationPath).Returns("/");
_httpRequestMock.SetupGet(x => x.Url).Returns(new Uri("http://localhost/a", UriKind.Absolute));
_httpRequestMock.SetupGet(x => x.ServerVariables).Returns(new NameValueCollection());
_httpResponseMock.Setup(x => x.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(x => x);
var routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
var helper = new UrlHelper(new RequestContext(_httpContextMock.Object, new RouteData()), routes);
_httpContextMock.SetupGet(x => x.Request).Returns(_httpRequestMock.Object);
_httpContextMock.SetupGet(x => x.Response).Returns(_httpResponseMock.Object);
// https://stackoverflow.com/questions/4066184/httpresponsebase-headers-are-empty-when-running-test
_httpResponseMock.Setup(r => r.OutputStream).Returns(new MemoryStream());
_httpResponseMock.Setup(r => r.Headers).Returns(new NameValueCollection());
var userStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new Mock<ApplicationUserManager>(userStore.Object);
var authenticationManager = new Mock<IAuthenticationManager>();
var signInManager = new Mock<ApplicationSignInManager>(userManager.Object, authenticationManager.Object);
var data = new Dictionary<string, object>()
{
{"a", "b"} // fake whatever you need here.
};
// https://stackoverflow.com/a/28574389/177416
_controller = new AccountController(
userManager.Object, signInManager.Object)
{
ControllerContext = new ControllerContext()
{
HttpContext = _httpContextMock.Object
}
};
_controller.ControllerContext.HttpContext.Items["owin.Environment"] = data;
_controller.Url = helper;
}
And here's my test method:
[TestMethod]
public async Task NoViewReturnedForFailedLogin()
{
// Arrange
var claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, "fake@fake.org"));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
SetContexts(claimsPrincipal);
var vm = new LoginViewModel
{
Email = "faker@gmail.com",
Password = "Faker9999!",
RememberMe = false
};
_controllerContextMock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(vm.Email);
_controllerContextMock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(false);
// Act
var result = await _controller.Login(vm, "http://www.google.com") as RedirectToRouteResult;
// Assert
// ? What do I assert?
}
As you can see, I have mock userStore
, userManager
, authenticationManager
, and signInManager
. If I step through the Login
action, result
always returns SignInStatus.Success
.
Is there a way to have it fail? What should the test assert?