10

I am struggling to implement unit testing for action methods that incorporate User.Identity.Name functionality. The methods that I've come across fail because the properties that they suggest writing to throw 'read-only' errors (e.g. writing to HttpContext or the controller User)

I have an action method:

[Authorize]
        public async Task<ViewResult> EditProject(int projectId)
        {
            Project project = repository.Projects.FirstOrDefault(p => p.ProjectID == projectId);
            if (project != null)
            {
            //HOW DO I MOCK USER.IDENTITY.NAME FOR THIS PORTION?
                var user = await userManager.FindByNameAsync(User.Identity.Name); 
                bool owned = await checkIfUserOwnsItem(project.UserID, user);
                if (owned)
                {
                    return View(project);
                }
                else
                {
                    TempData["message"] = $"User is not authorized to view this item";
                }
             }
            return View("Index");
        }

If I want to unit test this action method, how can I mock up the User.Identity object?

[Fact]
        public async Task Can_Edit_Project()
        {
            //Arrange
            var user = new AppUser() { UserName = "JohnDoe", Id = "1" };
            Mock<IRepository> mockRepo = new Mock<IRepository>();
            mockRepo.Setup(m => m.Projects).Returns(new Project[]
            {
                new Project {ProjectID = 1, Name = "P1", UserID = "1"},
                new Project {ProjectID = 2, Name = "P2", UserID = "1"},
                new Project {ProjectID = 3, Name = "P3", UserID = "1"},
            });
            Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
            Mock<UserManager<AppUser>> userMgr = GetMockUserManager();
            //Arrange
            ProjectController controller = new ProjectController(mockRepo.Object, userMgr.Object)
            {
                TempData = tempData.Object,
            };


           //HOW WOULD I MOCK THE USER.IDENTITY.NAME HERE?
           //The example below causes two errors:
           // 1) 'Invalid setup on a non-virtual (overridable in VB) member
           //mock => mock.HttpContext
           //and 2) HttpContext does not contain a definition for IsAuthenticated
           var mock = new Mock<ControllerContext>();
           mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(user.UserName);
          mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);

            //Act
            var viewResult = await controller.EditProject(2);
            Project result = viewResult.ViewData.Model as Project;

            //Assert
            Assert.Equal(2, result.ProjectID);
        }

EDIT: Making some progress by adding the code below.

var claims = new List<Claim>()
            {
                new Claim(ClaimTypes.Name, "John Doe"),
                new Claim(ClaimTypes.NameIdentifier, "1"),
                new Claim("name", "John Doe"),
            };
            var identity = new ClaimsIdentity(claims, "TestAuthType");
            var claimsPrincipal = new ClaimsPrincipal(identity);

            var mockPrincipal = new Mock<IPrincipal>();
            mockPrincipal.Setup(x => x.Identity).Returns(identity);
            mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

            var mockHttpContext = new Mock<HttpContext>();
            mockHttpContext.Setup(m => m.User).Returns(claimsPrincipal);

The User.Identity.Name is set properly now, but the line below still returns a user = null

var user = await userManager.FindByNameAsync(User.Identity.Name);

How can I make sure my mocked UserManager can return a mocked logged in user?

coolhand
  • 1,876
  • 5
  • 25
  • 46

3 Answers3

20

Set your fake User through ControllerContext

var context = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = fakeUser
    }
};

// Then set it to controller before executing test
controller.ControllerContext = context;
Fabio
  • 31,528
  • 4
  • 33
  • 72
10

To unit test my action method that uses

var user = await userManager.FindByNameAsync(User.Identity.Name); 

I needed to:

  1. set up my user

var user = new AppUser() { UserName = "JohnDoe", Id = "1" };

  1. Set up my HttpContext to give data to return user.UserName in the User.Identity.Name object in the controller
var claims = new List<Claim>()
            {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
                new Claim("name", user.UserName),
            };
var identity = new ClaimsIdentity(claims, "Test");
var claimsPrincipal = new ClaimsPrincipal(identity);

var mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(claimsPrincipal);
  1. Setup the mock UserManager to return the user object on the FindByNameAsync method
Mock<UserManager<AppUser>> userMgr = GetMockUserManager();
            userMgr.Setup(x => x.FindByNameAsync(It.IsAny<string>())).ReturnsAsync(user);

Edit:

public Mock<UserManager<AppUser>> GetMockUserManager()
{
    var userStoreMock = new Mock<IUserStore<AppUser>>();
    return new Mock<UserManager<AppUser>>(
        userStoreMock.Object, null, null, null, null, null, null, null, null);
}
JGH
  • 15,928
  • 4
  • 31
  • 48
coolhand
  • 1,876
  • 5
  • 25
  • 46
2

You can create fakeContext and use that. See below:

var fakeContext = new Mock<HttpContextBase>();
var fakeIdentity = new GenericIdentity("User");
var principal = new GenericPrincipal(fakeIdentity, null);
fakeContext.Setup(x => x.User).Returns(principal);
var projectControllerContext = new Mock<ControllerContext>();
projectControllerContext.Setup(x => 
  x.HttpContext).Returns(fakeContext.Object);  
Boxiom
  • 2,245
  • 6
  • 40
  • 51
Fancy5g
  • 99
  • 4
  • Should this be working in Core? I get an error on `HttpContextBase` (namespace not found) and an error on `principal` 'cannot convert from GenericPrincipal to ?' – coolhand Mar 16 '18 at 22:19
  • 3
    this wont work in asp net core. – prashant Aug 28 '18 at 03:43
  • You can piece together with other answers and make it work. At least the `var fakeIdentity = new GenericIdentity("tester");` part is helpful. Now you can add `var fakeUser = new ClaimsPrincipal(fakeIdentity);` and make the answer by Fabio work. – Weihui Guo Sep 18 '20 at 14:48