用于在 MVC5 中模拟 ConfirmEmailAsync 和其他 UserManager 方法的接口



我正在尝试对这个控制器方法进行单元测试,该方法在当前的MVC项目中开箱即用。

[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
    if (userId == null || code == null)
    {
        return View("Error");
    }
    var result = await UserManager.ConfirmEmailAsync(userId, code);
    return View(result.Succeeded ? "ConfirmEmail" : "Error");
}

帐户控制器有一个构造函数,它将采用应用程序用户管理器和应用程序登录管理器作为参数,以及与用于测试的私有资源库的匹配属性。但是,我不知道如何模拟ConfirmEmailAsync方法。

您可以在 Identity 命名空间中模拟各种接口:

var store = new Mock<IUserStore<ApplicationUser>>();
store.As<IUserEmailStore<ApplicationUser>>()
            .Setup(x => x.FindByIdAsync("username1"))
            .ReturnsAsync((ApplicationUser)null);
var mockManager = new ApplicationUserManager(store.Object);
AccountController ac = new AccountController(mockManager, null, GetMockRepository().Object, GetMockLogger().Object);

但是我找不到或弄清楚我需要哪个界面才能创建ConfirmEmailAsync的模拟。

我该怎么做?作为参考,有没有一种好的方法可以找出这些方法在哪些接口上,以便模拟和测试它们?

ConfirmEmailAsync当前不是框架中接口的一部分。它位于 UserManager<TUser, TKey> 类中,该类是标识框架的基类。

我的解决方案?

抽象所有事物

通过将标识的大部分功能抽象到自己的项目中来解决这个问题,这样我就可以更轻松地对其进行单元测试,并在其他项目中重用抽象。看完这篇文章后我有了这个想法

持久性无知 ASP.NET 模式标识

然后,我微调了这个想法以满足我的需求。我基本上只是将 asp.net.identity 中我需要的所有内容交换为我的自定义接口,这些接口或多或少地反映了框架提供的功能,但具有更容易模拟的优点。

IIdentityUser

/// <summary>
///  Minimal interface for a user with an id of type <seealso cref="System.String"/>
/// </summary>
public interface IIdentityUser : IIdentityUser<string> { }
/// <summary>
///  Minimal interface for a user
/// </summary>
public interface IIdentityUser<TKey>
    where TKey : System.IEquatable<TKey> {
    TKey Id { get; set; }
    string UserName { get; set; }
    string Email { get; set; }
    bool EmailConfirmed { get; set; }
    string EmailConfirmationToken { get; set; }
    string ResetPasswordToken { get; set; }
    string PasswordHash { get; set; }
}

IIdentityManager

/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager : IIdentityManager<IIdentityUser> { }
/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager<TUser> : IIdentityManager<TUser, string>
    where TUser : class, IIdentityUser<string> { }
/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager<TUser, TKey> : IDisposable
    where TUser : class, IIdentityUser<TKey>
    where TKey : System.IEquatable<TKey> {
    Task<IIdentityResult> AddPasswordAsync(TKey userid, string password);
    Task<IIdentityResult> ChangePasswordAsync(TKey userid, string currentPassword, string newPassword);
    Task<IIdentityResult> ConfirmEmailAsync(TKey userId, string token);
    //...other code removed for brevity
}

IIdentityResult

/// <summary>
/// Represents the minimal result of an identity operation
/// </summary>
public interface IIdentityResult : System.Collections.Generic.IEnumerable<string> {
    bool Succeeded { get; }
}

在标识管理器的默认实现中,我只是包装了ApplicationManager,然后在我的类型和 asp.net.identity 类型之间映射结果和功能。

public class DefaultUserManager : IIdentityManager {
    private ApplicationUserManager innerManager;
    public DefaultUserManager() {
        this.innerManager = ApplicationUserManager.Instance;
    }
    //..other code removed for brevity
    public async Task<IIdentityResult> ConfirmEmailAsync(string userId, string token) {
        var result = await innerManager.ConfirmEmailAsync(userId, token);
        return result.AsIIdentityResult();
    }
    //...other code removed for brevity
}

免责声明:我在Typemock工作。

实际上,如果您使用的是Typemock,则不需要任何接口,您只需要伪造所需的IdentityResult并更改异步方法"ConfirmEmailAsync"的行为,例如检查未确认电子邮件场景的测试:

[TestMethod, Isolated]
public async Task TestWhenEmailIsBad_ErrorMessageIsShown()
{
    // Arrange
    // Create the wanted controller for testing and fake IdentityResult
    var controller = new aspdotNetExample.Controllers.AccountController();
    var fakeIdentityRes = Isolate.Fake.Instance<IdentityResult>();
    // Fake HttpContext to return a fake ApplicationSignInManager
    var fakeSIM = Isolate.WhenCalled(() => controller.UserManager).ReturnRecursiveFake();
    // Modifying the behavior of ConfirmEmailAsync to return fakeIdentityRes
    Isolate.WhenCalled(() => fakeSIM.ConfirmEmailAsync("", "")).WillReturn(Task.FromResult<IdentityResult>(fakeIdentityRes));
    Isolate.WhenCalled(() => fakeIdentityRes.Succeeded).WillReturn(false);
    // Act
    var result = await controller.ConfirmEmail("", "") as ViewResult;
    // Assert
    Assert.AreEqual("Error", result.ViewName);
}    

相关内容

  • 没有找到相关文章

最新更新