【问题标题】:CustomMembershipProvider not working from Unit testingCustomMembershipProvider 在单元测试中不起作用
【发布时间】:2014-06-10 01:01:33
【问题描述】:

编写需要通过我的 CustomMembershipProvider 访问数据库的单元测试。

edit-

 public class CustomMembershipProvider : MembershipProvider
    {
         public override bool ValidateUser(string username, string password)
        {

            using (var usersContext = new UsersContext())
            {
                var requiredUser = usersContext.GetUser(username, password);
                var userApproved = usersContext.GetUserMem(username);
                if (userApproved == null) return false;
                return (requiredUser != null && userApproved.IsApproved != false);
            }
        }
    }

   [TestFixture]
    public class AccountControllerTest
    {

        [Test]
        public void ShouldNotAcceptInvalidUser()
        {
            // OPTION1
            Mock<IMembershipService> membership = new Mock<IMembershipService>();
            //OPTION2
            // Mock<AccountMembershipService> membership = new Mock<AccountMembershipService>();

            membership.Setup(m => m.ValidateUser(It.IsAny<string>(), It.IsAny<string>()))
                      .Returns(false);
            var logonModel = new LoginModel() { EmailorUserName = "connorgerv", Password = "pasdsword1" };
            var controller = new AccountController(membership.Object);

            // Act
            var result = controller.Login(logonModel,"Index") as RedirectResult;

            // Assert
            Assert.That(result.Url, Is.EqualTo("Index"));
            Assert.False(controller.ModelState.IsValid);
            Assert.That(controller.ModelState[""],
                        Is.EqualTo("The user name or password provided is incorrect."));
        }

        [Test]
        public void ExampleForMockingAccountMembershipService()
        {
            var validUserName = "connorgerv";
            var validPassword = "passwordd1";
            var stubService = new Mock<CustomMembershipProvider>();
            bool val = false;

            stubService.Setup(x => x.ValidateUser(validUserName, validPassword)).Returns(true);

            Assert.IsTrue(stubService.Object.ValidateUser(validUserName, validPassword));
        }

    }



public class AccountController : Controller
    {
        public IMembershipService MembershipService { get; set; }

        public AccountController(IMembershipService service){

            MembershipService=service;
        }

        protected override void Initialize(RequestContext requestContext)
        {
            if (MembershipService == null) { MembershipService = new AccountMembershipService(); }

            base.Initialize(requestContext);
        }

        public ActionResult Index()
        {
            return RedirectToAction("Profile");
        }


        public ActionResult Login()
        {
            if (User.Identity.IsAuthenticated)
            {
                //redirect to some other page
                return RedirectToAction("Index", "Home");
            }
            return View();
        }

        //
        // POST: /Account/Login

        [HttpPost]
        public ActionResult Login(LoginModel model, string ReturnUrl)
        {
            if (ModelState.IsValid)
            {
                if (MembershipService.ValidateUser(model.EmailorUserName, model.Password))
                {

                    SetupFormsAuthTicket(model.EmailorUserName, model.RememberMe);

                    if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
                        && !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
                    {
                        return Redirect(ReturnUrl);
                    }
                    return RedirectToAction("Index", "Home");
                }
                ModelState.AddModelError("", "The user name or password provided is incorrect.");
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }
    }


  public class AccountMembershipService : IMembershipService
    {
        private readonly MembershipProvider _provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }


        public virtual bool ValidateUser(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
            if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");

            return _provider.ValidateUser(userName, password);
        }

    }

Membership in web.config of main application

<membership defaultProvider="CustomMembershipProvider">
  <providers>
    <clear />
    <add name="CustomMembershipProvider" type="QUBBasketballMVC.Infrastructure.CustomMembershipProvider" connectionStringName="UsersContext" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
  </providers>
</membership>

 public class CustomMembershipProvider : MembershipProvider
{
     public override bool ValidateUser(string username, string password)
    {

        using (var usersContext = new UsersContext())
        {
            var requiredUser = usersContext.GetUser(username, password);
            var userApproved = usersContext.GetUserMem(username);
            if (userApproved == null) return false;
            return (requiredUser != null && userApproved.IsApproved != false);
        }
    }
}

当我在未注释 Option1 的情况下运行 ShouldNotAcceptInvalidUser() 时会发生什么我可以看到 MembershipService 是 AccountController 中的 Mock&lt;IMembershipService&gt; 但它从未在登录操作中进入 MembershipService.ValidateUser。

当我在未注释选项 2 的情况下运行时,会发生同样的事情,除了 MembershipService 在 accountcontroller 中是 Mock&lt;AccountMembershipService&gt; 并且它使用 null 参数访问 AccountMembership 构造函数,而后者又将 SqlMembershipProvider 设置为 Membership.ProviderSystem.Web.Security.SqlMembershipProvider

另外,ExampleForMockingAccountMembershipService()CustomMembershipProvider 中似乎根本没有使用ValidateUser 方法并且总是返回true。

希望这足以看出我哪里出错了! :/

【问题讨论】:

  • 您提供的链接中的信息如何不起作用? (例如,_provider 是否仍显示为SqlMembershipProvider?)这是在为测试项目添加配置后的 app.config 吗?
  • 对不起,我从来没有看到你的回复,是的,提供者仍然显示为 SqlMembershipProvider!我已经尝试将配置添加到 app.config 并且没有运气!这段代码应该工作吗? [Test] public void ExampleForMockingAccountMembershipService() { var validUserName = "connorgerv"; var validPassword = "passwordd1"; var stubService = new Mock&lt;CustomMembershipProvider&gt;(); stubService.Setup(x =&gt; x.ValidateUser(validUserName, validPassword)).Returns(true); Assert.IsTrue(stubService.Object.ValidateUser(validUserName, validPassword)); }
  • 我不是想问一个愚蠢的问题,但是您是否对 test 项目中的app.config 进行了配置更改? (也就是说,不是定义了AccountMembershipService 的项目,而是定义了你的测试的项目。) 至于你的代码是否应该工作,没有看到CustomMembershipProvider 的代码很难说。话虽如此,如果您尝试测试CustomMembershipProvider,那么您不应该嘲笑它。
  • Lilshieste,我已经用我的代码的 sn-p 更新了帖子。我确实将成员身份配置文件添加到了测试项目的 app.config 中,但是当我在某处读到这没有必要时将其删除?
  • 感谢您发布代码。我很困惑——我们可能在谈论两件不同的事情。您在测试中没有看到SqlMembershipProvider (ExampleForMockingAccountMembershipService),对吗? (在您的 OP 中,您表示您在 AccountMembershipService._provider 中看到了它,但 AccountMembershipService 没有出现在您在上面评论中发布的测试代码中。)

标签: asp.net-mvc unit-testing app-config custom-membershipprovider


【解决方案1】:

感谢您提供代码。我想我对你现在想要做的事情有了更好的把握。

对于您的ShouldNotAcceptInvalidUser 测试,您绝对应该模拟IMembershipService 而不是AccountMembershipService(选择选项1 而不是选项2)。由于您的控制器是您的 SUT,因此它应该是测试中唯一的“真实”类,以尽量减少移动部件的数量。

使用选项 1,没有理由期望 MembershipService.ValidateUser 会进入任何代码。 MembershipService 是一个模拟对象 - 您已经明确告诉它在调用该方法时始终返回 false。根据此处的代码,并使用选项 1,我希望此测试能够通过。

在你的另一个测试中,ExampleForMockingAccountMembershipService,你在嘲笑你的 SUT,这是你应该做的事情。您的 SUT 应该是测试中唯一的“真实”对象。这意味着应该模拟所有协作对象,让 SUT 成为唯一做任何有意义的事情的对象。 (这样,如果测试失败,您就可以确定这是因为 SUT 中的错误。)

(注意:ValidateUser 在这里总是返回 true,因为你模拟了 SUT,并明确告诉它总是返回 true。这就是为什么模拟你的 SUT 从来都不是一个好主意 - 模拟会改变行为您正在尝试测试。)

根据您提供的代码,我猜您嘲笑CustomMembershipProvider 的原因是因为它没有完全实现其抽象基类MembershipService。如果确实如此,那么您将需要手动实现缺少的方法,而不是依赖模拟框架来提供默认实现。

这就是我相信您打算这个测试的样子:

    [Test]
    public void ExampleForMockingAccountMembershipService()
    {
        var validUserName = "connorgerv";
        var validPassword = "passwordd1";
        var sut = new CustomMembershipProvider();

        Assert.IsTrue(sut.ValidateUser(validUserName, validPassword));
    }

这里需要注意的是CustomMembershipProvider 实例化了它的一个依赖项:UsersContext。在单元测试中,由于 CustomMembershipProvider 是您的 SUT,因此您需要模拟它的所有依赖项。在这种情况下,您可以使用 dependency injection 传递负责创建此依赖项的对象(例如,IUsersContextFactory),并在测试中使用模拟工厂和上下文。

如果您不想走这条路,那么请注意,您的测试可能会因为CustomMembershipProvider 中的错误而失败UsersContext 中的错误。

因此,您的测试中的一般逻辑是合理的;问题主要源于对模拟对象在测试中的角色的混淆。起初这是一个很难理解的概念,但是当我学习这个时,这里有一些资源对我有帮助:

"Dependency Injection in .Net" by Mark Seemann

"Test Doubles" by Martin Fowler

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-23
  • 2013-02-20
  • 1970-01-01
相关资源
最近更新 更多