【问题标题】:Moq usermanager missing IUserEmailStore起订量用户管理器缺少 IUserEmailStore
【发布时间】:2019-04-10 10:23:06
【问题描述】:

我在测试什么。

这是一个登录到联合网关的身份服务器项目。我不控制这个网关,并且他们没有向我返回正确的声明,我需要验证用户登录。我希望能够测试我是否可以处理这些错误。

例如,电子邮件声明丢失,否则我无法登录用户。

我创建了一个测试,测试电子邮件声明丢失返回错误。(工作正常)

现在我正在尝试测试事物的另一面。如果声明确实存在,它应该返回与返回的声明匹配的用户。

我们正在测试的方法

public static async Task<(ApplicationUser user, string provider, string providerUserUserName, IEnumerable<Claim> claims, string message)> FindUserFromExternalProvider(AuthenticateResult result, UserManager<ApplicationUser> userManager, ILogger<SegesExternalController> logger)
    {
        var externalUser = result.Principal;

        // try to determine the unique id of the external user (issued by the provider)
        var eMailClaim = externalUser.FindFirst(SegesSettingsConstants.SegesEmailClaimName);

        if(eMailClaim == null) return (null, null, null, null, $"{SegesSettingsConstants.SegesEmailClaimName} claim not found.");

        // remove the user id claim so we don't include it as an extra claim if/when we provision the user
        var claims = externalUser.Claims.ToList();
        claims.LogSegesClaims(logger);

        claims.Remove(eMailClaim);
        // Should we remove more claims
        var provider = result.Properties.Items["scheme"];
        var providerUserUserName = eMailClaim.Value;

        var user = await userManager.FindByEmailAsync(providerUserUserName);  // Test Breaks here

        return (user, provider, providerUserUserName, claims, null);
    }

测试

[Fact]
public async void Federated_login_with_email_claim_return_no_error()
    {
        // Arrange

        var principal = new ClaimsPrincipal();
        principal.AddIdentity(new ClaimsIdentity(
            new Claim[] {
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "Testbruger til André"),
                new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", @"PROD\Salg43"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode", "8200"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality", "Aarhus N"),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "test@email.com"),
            },
            "FakeScheme"));
        var authenticateResult = AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties() { Items = { { "scheme", "fed" } } }, "FakeScheme"));


        var exprectUser = new ApplicationUser()
        {
            UserName = "test@email.com",
            NormalizedUserName = "TEST@EMAIL.COM",
            NormalizedEmail = "TEST@EMAIL.COM",
            Email = "test@email.com",
            Id = 123,
            EmailConfirmed = true
        };

        var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
        var mockQueryableUserStore = new Mock<IQueryableUserStore<ApplicationUser>>();

        var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
        mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
        var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);


        var logger = new Logger<ExternalController>(new LoggerFactory());

        // Act
        var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManager, logger);

        // Assert
        user.ShouldNotBeNull();
    }

上面的问题。

我正在尝试为我的单元测试设置一个用户管理器

var exprectUser = new ApplicationUser()
        {
            UserName = "test@email.com",
            NormalizedUserName = "TEST@EMAIL.COM",
            NormalizedEmail = "TEST@EMAIL.COM",
            Email = "test@email.com",
            Id = 123,
            EmailConfirmed = true
        };

var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

但是当我正在测试的方法试图找到用户时。

var findUser = await userManager.FindByEmailAsync("test@test.com");

会报错

消息:System.NotSupportedException:存储未实现 IUserEmailStore。

如何在我的 moq 用户管理器中实现 IUserEmailStore?

我的单元测试项目确实包含最新的 EntityFramework 包。

尝试另一种方式。

var founduser = userManager.Users.FirstOrDefault(e => e.Email.Equals("test@test.com", StringComparison.InvariantCultureIgnoreCase));

结果

System.NotSupportedException : Store 没有实现 IQueryableUserStore。

我想我一定是摸错了。

从评论更新

好的,我可以将 IUserEmailStore 起订量,但我不确定我应该如何处理它

var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();

【问题讨论】:

  • 模拟IUserEmailStore&lt;TUser&gt; Interface,派生自IUserStore&lt;TUser&gt;
  • 好的,用它做什么? var mockEmailStore = new Mock>();
  • 问题是您需要知道管理器如何访问该依赖项,以便为您设置预期行为的安全路径。但是我相信这可能是XY problem。你到底想测试什么?你需要经理做什么?
  • @Nkos 说了什么:你为什么要测试 UserManager。微软确实做了很多工作来测试它并用单元测试覆盖它的实现。作为库的使用者,对库的实现进行单元测试不是您的任务。您应该对自己的代码进行单元测试,而不是其他人的代码
  • 但是用户管理器不是你的代码,它是来自微软的代码。当您需要测试其他人的代码时,您的测试用例存在缺陷。如果你不相信微软做对了,如果你有一个你认为可能是错误的极端案例,也许可以使用另一个库或提交问题?

标签: c# asp.net-core asp.net-identity moq xunit


【解决方案1】:

我设法创建了一个完整的起订量用户管理器,让我可以搜索电子邮件

 public class MoqUserManager : UserManager<ApplicationUser>
    {
        public MoqUserManager(IUserStore<ApplicationUser> userStore) : base(userStore,
                new Mock<IOptions<IdentityOptions>>().Object,
                new Mock<IPasswordHasher<ApplicationUser>>().Object,
                new IUserValidator<ApplicationUser>[0],
                new IPasswordValidator<ApplicationUser>[0],
                new Mock<ILookupNormalizer>().Object,
                new Mock<IdentityErrorDescriber>().Object,
                new Mock<IServiceProvider>().Object,
                new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
        { }

        public override Task<ApplicationUser> FindByEmailAsync(string email)
        {
            return Task.FromResult(new ApplicationUser { Email = email });
        }    
    }

这给了我

var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new FakeUserManager(mockUserStore.Object);

所以现在我可以验证是否从我的身份服务器返回了与联合登录用户匹配的正确用户。

【讨论】:

  • 也可以。很高兴你能想出办法。
【解决方案2】:

好的,问题在于更新后的问题

var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

这不是创建模拟,而是UserManager&lt;T&gt; 的实际实例。

你必须这样做

var userManagerMock = new Mock<UserManager<ApplicationUser>>(mockUserStore.Object, null, null, null,  null, null, null, null, null);

然后进行设置

userManagerMock.Setup(um => um.FindByEmailAsync("test@email.com)).Returns(exprectUser)

并将userManagerMock.Object 传递给您的

var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManagerMock.Object, logger);

在模拟时,您永远不想在外部依赖项上调用 new 而是模拟它,因为那时您无法更改其针对特定测试的行为。 UserManager&lt;T&gt; 应该将所有或大部分公共属性作为虚拟属性,因此您可以覆盖它们。

【讨论】:

  • 您实际上应该知道的是,您也编辑了this answer :P 另请参阅this source,了解 Microsoft 如何创建 UserManager 的 Mock 实例(用户存储是必需的,因为有一个空签入构造函数)
猜你喜欢
  • 1970-01-01
  • 2013-09-30
  • 2013-03-03
  • 2016-09-29
  • 2018-12-15
  • 2019-06-20
  • 2010-11-30
  • 1970-01-01
  • 2011-11-06
相关资源
最近更新 更多