2

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?

Alex
  • 34,699
  • 13
  • 75
  • 158
  • 2
    You need to setup the mock for SignInManager.PasswordSignInAsync to return something else – Aman B Jan 09 '18 at 13:40
  • 3
    signInManager.Setup(p => PasswordSignInAsync().ReturnsAsync(SignInStatus.Failure); – Aman B Jan 09 '18 at 13:42
  • 3
    You are mocking way too many dependencies while the method under test only depends on the `SignInManager`. Setup that dependency to behave as desired for the test and your done. As for what to assert, look at the expected route of the code and assert that the result is as expected. – Nkosi Jan 09 '18 at 13:45
  • 1
    From the mocks created it seems your account controller is tightly coupled to implementation concerns which would also explain why you have to mock so many dependencies. You classes should depend on abstractions and not concrete implementations. – Nkosi Jan 09 '18 at 13:49
  • @AmanB, I don't see `.ReturnsAsync(SignInStatus.Failure)` as a method on the `signInManager` or even a `Returns` – Alex Jan 09 '18 at 14:02
  • @Alex this is the mock object in your setcontexts method – Aman B Jan 09 '18 at 14:08
  • 1
    Amount of set up suggests that you are testing on the wrong level or wrong thing. Step back and think what you are actually trying to test and why. If you trying to test `SignInManager` - don't this is part of identity library and is already tested for you. – trailmax Jan 09 '18 at 15:05
  • @Alex any luck making the unit test work? – Aman B Jan 10 '18 at 17:50
  • @AmanB, not really. The correct syntax was `signInManager.Setup(x => x.PasswordSignInAsync(It.IsAny(), It.IsAny(), true, true)).Returns(Task.FromResult(SignInStatus.Failure));`. It ran into other issues, where it had a null ref on `GetOwinContext()`, and getting a mock OWIN context was a bridge too far for me. Then how do you test that an action returns a `ChllengeResult`? – Alex Jan 10 '18 at 17:55

0 Answers0