web-dev-qa-db-ja.com

ユニットテスト用のASP.NET CoreのUser.Identityのモック

_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が偽装したログインユーザーを確実に返すことができるようにするにはどうすればよいですか?

6
coolhand

偽物を設定するUserからControllerContext

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

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

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

を使用する私のアクションメソッドを単体テストするには

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

私はする必要がありました:

1)ユーザーを設定する

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

2)コントローラのuser.UserNameオブジェクトでUser.Identity.Nameを返すデータを提供するようにHttpContextを設定します

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

3)UserManagerメソッドでuserオブジェクトを返すようにモックFindByNameAsyncを設定します

Mock<UserManager<AppUser>> userMgr = GetMockUserManager();
            userMgr.Setup(x => x.FindByNameAsync(It.IsAny<string>())).ReturnsAsync(user);
1
coolhand