ASP.NET Core 中用于单元测试的模拟 User.Identity [重复]

Mock User.Identity in ASP.NET Core for Unit Testing [duplicate]

提问人:coolhand 提问时间:3/17/2018 最后编辑:coolhand 更新时间:11/1/2021 访问量:22190

问:

我正在努力为包含功能的操作方法实现单元测试。我遇到的方法失败了,因为它们建议编写的属性会引发“只读”错误(例如,写入或控制器User.Identity.NameHttpContextUser)

我有一个操作方法:

[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.Nameuser = null

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

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

C# 单元测试 ASP.NET-CORE-MVC XUNIT

评论

0赞 Fabio 3/17/2018
只需通过 ControllerContext 进行设置

答:

2赞 Fancy5g 3/17/2018 #1

您可以创建 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);  

评论

0赞 coolhand 3/17/2018
这应该在 Core 中工作吗?我收到错误(找不到命名空间)和错误“无法从 GenericPrincipal 转换为?HttpContextBaseprincipal
3赞 prashant 8/28/2018
这在 ASP NET Core 中不起作用。
0赞 Weihui Guo 9/18/2020
你可以与其他答案拼凑在一起,让它发挥作用。至少这部分是有帮助的。现在,您可以添加并使 Fabio 的答案起作用。var fakeIdentity = new GenericIdentity("tester");var fakeUser = new ClaimsPrincipal(fakeIdentity);
20赞 Fabio 3/17/2018 #2

设置你的假货UserControllerContext

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

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

评论

1赞 coolhand 3/17/2018
我是否必须使用 from 的隐式转换才能使其工作?这给了我一个“方法或操作未实现”错误AppUserClaimsPrincipal
4赞 Andrew Gray 9/3/2019
fakeUser 设置在哪里?
0赞 crthompson 12/21/2019
@AndrewGray大概是这样的fakeUsernew User(any special settings you want)
1赞 Weihui Guo 9/18/2020
要设置 fakeUser,请参阅 Fancy5g 的回答和我的评论。
11赞 coolhand 3/17/2018 #3

要对我的操作方法进行单元测试,请使用

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

我需要:

  1. 设置我的用户

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

  1. 设置我的 HttpContext 以提供要在控制器中的对象中返回的数据user.UserNameUser.Identity.Name
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. 设置模拟以返回方法上的对象UserManageruserFindByNameAsync
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);
}

评论

0赞 Christophe Chenel 4/28/2021
什么是 : GetMockUserManager() ?我只是无法访问它,它不存在
1赞 coolhand 4/28/2021
@ChristopheChenel 返回 mocked 的自定义函数是自定义函数。我会把它添加到答案中。UserManager