【问题标题】:Unit testing custom MembershipProvider.ValidateUser, using code in Global.asax使用 Global.asax 中的代码对自定义 MembershipProvider.ValidateUser 进行单元测试
【发布时间】:2012-02-01 13:50:24
【问题描述】:

我有一个单元测试来检查我的 AccountController.LogIn 方法。返回重定向结果表示成功,否则返回查看结果。

测试总是失败,因为返回类型总是 viewresult,即使测试应该返回成功,因为凭据是有效的,但是我无法确定问题出在哪里。 我的测试方法: CustomerRepositoryTest.cs

[TestMethod]
public void Can_Login_With_Valid_Credentials()
{
     //  Arrange
     Mock<IAddressRepository> mockAddressRepository = new Mock<IAddressRepository>();
     Mock<ICustomerRepository> mockCustomerRepository = new Mock<ICustomerRepository>();
     Mock<IOrderRepository> mockOrderRepository = new Mock<IOrderRepository>();

     LoginViewModel model = new LoginViewModel
     {
         Email = "me@5.com",
         Password = "password"
     };

     AccountController target = new AccountController(mockCustomerRepository.Object, mockAddressRepository.Object, mockOrderRepository.Object);

   //  Act
    ActionResult result = target.LogIn(model);

    //  Assert
    Assert.IsInstanceOfType(result, typeof(RedirectResult));
    Assert.AreEqual("", ((RedirectResult)result).Url);
    }

当我运行测试时,当我调用 ValidateUser 时,它在 My AccountController 登录方法中失败 AccountController.cs

if (Membership.ValidateUser(LoginModel.Email, LoginModel.Password))
{
  ...
 return RedirectToRoute(new
 {
    controller = "Account",
    action = "Details"
 });
}
else
{
   return View();
}

我的自定义 MembershipProvider ValidateUser 如下所示:

AccountMembershipProvider.cs

public class AccountMembershipProvider : MembershipProvider
{
     [Inject]
    public ICustomerRepository repository { get; set; }

    public override bool ValidateUser(string username, string password)
    {
       var cust = repository.GetAllCustomers().SingleOrDefault..

当我正常运行应用程序(即不测试)时,登录工作正常。在应用程序中,我将 CustomerRepository 注入到 Global.asax 中的自定义成员资格提供程序中:

    public class MvcApplication : System.Web.HttpApplication
    {
      private IKernel _kernel = new StandardKernel(new MyNinjectModules());

      internal class MyNinjectModules : NinjectModule
      {
        public override void Load()
        {
            Bind<ICustomerRepository>().To<CustomerRepository>();
        }
      }

      protected void Application_Start()

  {
   _kernel.Inject(Membership.Provider);
   ...

是否在单元测试时不运行 Global.asax 代码?所以我的自定义提供程序没有被注入,因此失败了?

更新 我模拟了我的 Provider 类并将模拟的 CustomerRepository 对象传递给它。

Mock<AccountMembershipProvider> provider = new Mock<AccountMembershipProvider>();

provider.Object.repository = mockCustomerRepository.Object;

然后我为我要测试的方法创建了一个设置:

 mockCustomerRepository.Setup(m => m.IsValidLogin("me@5.com", "password")).Returns(true);

但不幸的是,我每次仍然失败。回答关于我是否需要一个真实的或模拟的对象进行测试的问题 - 我并不挑剔,我只是想让它在此刻工作!

更新 2

我进行了这些更改,虽然它仍然失败,但它让我能够确定具体问题。在调试测试时,我发现当我调用覆盖时

Membership.ValidateUser(LoginModel.Email, LoginModel.Password)

Membership.Provider 是 SqlMembershipProvider 类型(可能是默认类型),因此验证失败。

如果我将提供程序转换为我的自定义提供程序...

((AccountMembershipProvider)Membership.Provider).ValidateUser(LoginModel.Email, LoginModel.Password)

我在运行测试时收到 InvalidCastException。因此,我模拟的 AccountMembershipProvider 似乎没有用于测试,而是使用了默认提供程序。

我想你已经在评论中指出了这一点:

// set your mock provider in your AccountController

但是我不确定您的确切意思 - 我的 AccountController 上没有属性可将提供程序分配给,而且我没有将其注入到构造函数中。

【问题讨论】:

  • 只是为了让您知道,我在当天早些时候的回答中添加了更多信息并进行了编辑。不要犹豫,要求澄清或更具体的信息。
  • @Gilles - 我实施了你的建议(见更新)。他们为我澄清了很多,尽管不幸的是每次都失败了!我需要为 Provider 和 Repository 类定义设置方法吗?
  • 我用一个例子更新了我的帖子。基本上,如果您的提供程序被模拟,则无需为其依赖项创建模拟,因为模拟对象不会调用它们。
  • @Gilles - 查看更新。测试仍然使用默认提供程序而不是我的自定义提供程序。非常感谢您在这方面的帮助,谢谢。
  • 在 Membership.ValidateUser 中,ValidateUser 是静态方法吗?

标签: asp.net-mvc-3 unit-testing ninject moq


【解决方案1】:

你原来的问题: “是不是在单元测试时 Global.asax 代码没有运行?所以我的自定义提供程序没有被注入,因此失败了?”

我的回答:

是的。

ASP.Net 和 ISS 在运行时使用 global.asax 文件。它在服务器收到第一个请求时编译。

当您进行测试时,您不是在 ISS 中运行的 ASP.Net Web 应用程序的上下文中,而是在测试会话中运行的程序中。这意味着您的 global.asax 不会被调用。

更重要的是,当你打电话时:

Mock<ICustomerRepository> mockCustomerRepository = new Mock<ICustomerRepository>();

Ninject 不会被称为填充导入。 Moq 将基于接口创建一个模拟。不会创建任何真实对象。

由于您没有定义任何设置方法,即:

mockCustomerRepository.Setup(mock => mock.MyAuthenticateMethod()).Returns(true);

您正在传递一个没有定义行为的模拟。 默认情况下,这些将返回 false。这可能解释了为什么你总是得到一个查看结果。

您需要做的是为您需要模拟的方法定义设置方法。

这些是调用时会调用你的 CustomerRepository 的方法:

target.LogIn(model);

另外请注意,您的 AccountMembershipProvider 不会获得 CustomerRepository 注入,因为不会使用 NInject。如果您正在测试 AccountController 并且它不是静态的(Moq 不适用于静态),您应该考虑模拟 AccountMembershipProvider。如果不能,则需要在测试中将模拟的 CustomerRepository 实例提供给 AccountMembershipProvider.repository。

另一种解决方案,除了创建 Moq 模拟之外,您还可以在测试中手动创建(使用新的)CustomerRepository 的真实实例。

你可以让 Ninject 来做,但现在为什么呢?您可以自己创建它,并且您知道要创建什么特定类型。

这一切都归结为您是否需要该测试的对象的模拟或真实实例。

更新:

如果您的提供程序被模拟,则无需在其上设置存储库。当您调用模拟时,不会调用真实对象。

你需要做的是这样的:

Mock<AccountMembershipProvider> provider = new Mock<AccountMembershipProvider>();
// set your mock provider in your AccountController

provider.Setup(m => m.ValidateUser("me@5.com", "password")).Returns(true);

更新 2:

我认为您已经非常接近完成您的测试了。如果没有看到你所有的代码(测试和测试中的类),我真的不能再给你帮助了。我也觉得我回答了您最初的问题,但如果您仍然遇到困难,您可能会通过提出与您当前正在解决的问题相关的新问题来获得更多帮助。

【讨论】:

  • 在我上次编辑中,我在答案中添加了更多信息。新部分以斜体显示。
  • 我觉得我最初的问题确实得到了回答,而且您超越简单地提供答案这一事实是您的功劳。
  • @Gilles,你说“//在你的 AccountController 中设置你的模拟提供者”,你能给我一个如何将提供者设置为控制器的例子
【解决方案2】:

在您的代码中,您只创建控制器,但不为成员资格运行初始化。我建议您使用方法 ValidateUser 和您需要的其他方法创建自己的 UserService,而不是使用静态类成员资格。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-27
    • 2010-11-08
    • 2020-12-07
    • 1970-01-01
    相关资源
    最近更新 更多