CustomMembershipProvider не работает из модульного тестирования

Написание модульных тестов, которые требуют доступа к базе данных через мой 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);
        }
    }
}

Что происходит, когда я бегу ShouldNotAcceptInvalidUser() с необязательным Option1 я вижу, что MembershipService является Mock<IMembershipService> в AccountController, но он никогда не входит в MembershipService.ValidateUser при действии входа в систему.

Когда я запускаю с необкомментированным option2, происходит то же самое, за исключением того, что MembershipService Mock<AccountMembershipService> в accountcontroller, и он попадает в AccountMembership Contstructor с нулевыми параметрами, который в свою очередь устанавливает SqlMembershipProvider как Membership.Provider является System.Web.Security.SqlMembershipProvider

Также ExampleForMockingAccountMembershipService() кажется, не ударил ValidateUserметод вообще в CustomMembershipProvider и всегда возвращает истину.

Надеюсь, этого достаточно, чтобы увидеть, где я иду не так!!:/

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, вы хотели бы издеваться над всеми его зависимостями. В этой ситуации вы можете использовать внедрение зависимостей для передачи объекта, ответственного за создание этой зависимости (например, IUsersContextFactory), и используйте фиктивную фабрику и контекст в своем тесте.

Если вы не хотите идти по этому пути, просто знайте, что ваш тест может не пройти из-за ошибки в CustomMembershipProvider или ошибка в UsersContext,

Итак, общая логика в ваших тестах звучит правильно; проблемы в основном возникают из-за путаницы в роли фиктивных объектов в ваших тестах. Сначала это сложная концепция, но вот некоторые ресурсы, которые помогли мне, когда я изучал это:

"Внедрение зависимостей в.Net" Марка Симанна

"Двойники теста" Мартина Фаулера

Другие вопросы по тегам