I am trying to unit test this controller method, which comes out of the box in current MVC projects.
[AllowAnonymous]
public async Task C
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
}
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);
}