我正在尝试对这个控制器方法进行单元测试,该方法在当前的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);
}