Interfaces for mocking ConfirmEmailAsync and other UserManager methods in MVC5

早过忘川 提交于 2019-12-01 22:33:45

问题


I am trying to unit test this controller method, which comes out of the box in current MVC projects.

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

The AccountController has a constructor which will take an ApplicationUserManager and a ApplicationSignInManager as parameters, and the matching properties with private setters to use for testing. However, I can't figure out how to mock out the ConfirmEmailAsync method.

You can mock various interfaces in the Identity namespace:

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

But I can't find or figure out which Interface I need in order to create a mock of ConfirmEmailAsync.

How do I go about this? And for reference, is there a good way of finding out which interfaces these methods are on in order to mock and test them?


回答1:


ConfirmEmailAsync is not currently part of an interface in the framework. It's in the UserManager<TUser, TKey> class which is the base class of Identity framework.

My solution?

Abstract all the things

I got around this by abstracting most of the functionality of identity into its own project so that I can unit test it easier and reuse the abstraction in other projects. I got the idea after reading this article

Persistence-Ignorant ASP.NET Identity with Patterns

I then fine tuned the idea to suit my needs. I basically just swapped everything i needed from asp.net.identity for my custom interfaces which more or less mirrored the functionality provided by the framework but with the advantage of easier mockability.

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

In my default implementation of the identity manager i simply wrapped the ApplicationManager and then mapped results and functionality between my types and the asp.net.identity types.

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
}



回答2:


Disclaimer: I work at Typemock.

Actually you don't need any interface if you are using Typemock, you just need to fake the IdentityResult you require and change the behavior of the asynchronous method "ConfirmEmailAsync", for example a test that checks the scenario of an Unconfirmed email:

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


来源:https://stackoverflow.com/questions/38463996/interfaces-for-mocking-confirmemailasync-and-other-usermanager-methods-in-mvc5

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!