【问题标题】:Mock ApplicationUserManager for Unit Testing MVC controllers用于单元测试 MVC 控制器的模拟 ApplicationUserManager
【发布时间】:2016-06-29 22:47:19
【问题描述】:

我想知道是否有人可以帮助我。

我正在为特定控制器编写单元测试。该控制器继承自 BaseController 并且该 BaseController 具有以下属性:

private ApplicationUserManager userManager;
public ApplicationUserManager UserManager 
{ 
    get { return this.userManager ??  this.Request.GetOwinContext().GetUserManager<ApplicationUserManager>(); } 
    set { this.userManager = value; } 
}

ApplicationUserManager 的 ctor 是:

public ApplicationUserManager(IUserStore<ApplicationUser> store, IIdentityMessageService emailService)
        : base(store)
    {
        this.EmailService = emailService;

        var dataProtectionProvider = Startup.DataProtectionProvider;
        this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
    }

这就是我模拟 ApplicatonUserManager 类的方法:

var store = new Mock<IUserStore<ApplicationUser>>();
var emailService = new Mock<IIdentityMessageService>();
var applicationUserManager = new Mock<ApplicationUserManager>(store.Object, emailService.Object);
this.targetController.UserManager = applicationUserManager.Object;
var dataprotectionprovided = new Mock<IDataProtectionProvider>();
applicationUserManager.Setup(r => r.UserTokenProvider).Returns(new DataProtectorTokenProvider<ApplicationUser, string>(dataprotectionprovided.Object.Create("ASP.NET Identity")));
this.targetController.UserManager = applicationUserManager.Object;

我试图模拟这个,但因为这不是虚拟财产(UserTokenProvider)它不允许我,我得到这个异常:

System.NotSupportedException: Invalid setup on a non-virtual (overridable in VB) member: r => r.UserTokenProvider

谁能帮我解决这个问题?我只是想模拟它以测试从具有该属性的 BaseController 继承的控制器。

谢谢

【问题讨论】:

  • 与其模拟 ApplicationUserManager,你可以为它创建另一个构造函数并通过备用构造函数实例化它吗?

标签: c# asp.net-mvc unit-testing moq asp.net-identity-2


【解决方案1】:

感谢您的帮助@bwyn

我已经设法根据您的建议破解它。刚刚为 ApplicationUserManager 创建了一个新的构造函数,如下所示:

public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
    {
    }

然后是单元测试:

var user = new ApplicationUser { User = new User { UserId = 1 } };

        var store = new Mock<IUserStore<ApplicationUser>>(MockBehavior.Strict);
        store.As<IUserStore<ApplicationUser>>().Setup(x => x.FindByIdAsync(It.IsAny<string>())).ReturnsAsync(user);
        this.targetController.UserManager = new ApplicationUserManager(store.Object);

谢谢大家!

【讨论】:

  • 我很高兴您找到了解决方案,但是如果其他开发人员尝试使用 ApplicationUserManager,您如何阻止他们使用您的测试构造函数创建实例(我认为这在实际实现中没有用) ?
  • 你好@mark_h 这是一个很好的观点。但是当我测试这个控制器时,我只是为了测试功能和逻辑,我将始终假设用户将始终登录,并且我们从 ApplicationUserManager 类中获得了一个有效的用户。在现实世界中,因为我有 2 个构造函数,所以我将使用正确的构造函数来注入 IUserStore 和 IIdentityMessageService。这回答了你的问题吗?谢谢
【解决方案2】:

正如您在回答中的一个 cmets 中指出的那样,创建额外的构造函数可能会导致其他开发人员使用错误的构造函数。

我可以建议您尝试以下方法而不是这个。

将原始 ApplicationUserManager 构造函数中的代码移动到新的受保护虚拟方法中。然后修改现在为空的构造函数来调用新的虚方法。

public class ApplicationUserManager
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store, IIdentityMessageService emailService)
: base(store)
    {
        CalledAfterConstruction();
    }

    protected virtual void CalledAfterConstruction()
    {
        this.EmailService = emailService;

        var dataProtectionProvider = Startup.DataProtectionProvider;
        this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
    }
}

现在,在继承 ApplicationUserManager 的单元测试时创建一个新的 MockApplicationUserManager 类,仅使用一个空方法覆盖继承的“CalledAfterConstruction”,并且您的答案几乎与您在答案中所做的一样。

public class MockClass : ApplicationUserManager
{
    public MockClass(IUserStore<ApplicationUser> store, IIdentityMessageService emailService) : base(store, emailService)
    {
    }

    protected override void CalledAfterContruction()
    {

    }
}

是的,这有点复杂,但它确实可以防止人们滥用你原来的类

【讨论】:

猜你喜欢
  • 2017-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多