Interfaces for mocking ConfirmEmailAsync and other UserManager methods in MVC5

前端 未结 2 419
被撕碎了的回忆
被撕碎了的回忆 2021-01-20 16:45

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

[AllowAnonymous]
public async Task C         


        
相关标签:
2条回答
  • 2021-01-20 16:56

    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
    }
    
    0 讨论(0)
  • 2021-01-20 17:09

    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);
    }    
    
    0 讨论(0)
提交回复
热议问题