在ASP.NET Core中为单元测试模拟User.Identity

10

我正在努力为涉及 User.Identity.Name 功能的操作方法实现单元测试。我找到的方法失败了,因为它们建议写入的属性会抛出“只读”错误(例如,写入 HttpContext 或控制器 User)。

我有一个操作方法:

[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");
        }

如果我想对这个操作方法进行单元测试,我该如何模拟 User.Identity 对象?

[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);
        }

编辑: 通过添加以下代码,取得了一些进展。

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);

User.Identity.Name现在已经正确设置,但下面的代码仍然返回user = null

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

我该如何确保我的模拟UserManager可以返回一个模拟的已登录用户?


只需通过ControllerContext进行设置。 - Fabio
3个回答

20

通过ControllerContext设置您的虚拟User

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

// Then set it to controller before executing test
controller.ControllerContext = context;

1
我是否必须使用从AppUserClaimsPrincipal的隐式转换才能使其工作?这会给我一个“方法或操作未实现”的错误。 - coolhand
4
fakeUser被设置在哪里? - Andrew Gray
@AndrewGray fakeUser 可以是类似于 new User(any special settings you want) 的东西。 - crthompson
1
要设置fakeUser,请参见Fancy5g的答案和我的评论。 - Weihui Guo

11

为了对使用的操作方法进行单元测试

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

我需要做以下几件事情:
  1. 设置我的用户

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

  1. 设置我的HttpContext,以便在控制器中的User.Identity.Name对象返回user.UserName数据
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. 配置模拟的 UserManager 对象,让其在 FindByNameAsync 方法调用时返回 user 对象。
Mock<UserManager<AppUser>> userMgr = GetMockUserManager();
            userMgr.Setup(x => x.FindByNameAsync(It.IsAny<string>())).ReturnsAsync(user);

编辑:

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);
}

什么是 GetMockUserManager()?我无法访问它,因为它不存在。 - Christophe Chenel
1
@ChristopheChenel 这是一个自定义函数,用于返回模拟的 UserManager。我会将其添加到答案中。 - coolhand

2
您可以创建一个假的上下文(fakeContext)并使用它。请参见以下内容:
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);  

这个在Core中应该能工作吗?我在HttpContextBase上得到了一个错误(命名空间未找到),并且在principal上得到了一个错误“无法将GenericPrincipal转换为?” - coolhand
3
这在ASP.NET Core中不起作用。 - prashant
你可以将其他答案拼凑在一起并使其正常工作。至少 var fakeIdentity = new GenericIdentity("tester"); 部分是有用的。现在你可以添加 var fakeUser = new ClaimsPrincipal(fakeIdentity); 并让 Fabio 的答案正常工作。 - Weihui Guo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接