Как Unit Test Членство в Asp.net?

Я новичок в модульном тестировании, и я пытаюсь проверить некоторые из своих компонентов членства в .NET, которые я пишу.

Итак, я пытаюсь проверить мой метод VerifyUser, который проверяет правильность учетных данных пользователей.

Итак, это выглядит так:

public bool VerifyUser(string userName, string password)
    {
        bool valid = Membership.ValidateUser(userName, password);
        return valid;
    }

И теперь каждый раз, когда я запускаю свой unit test, он терпит неудачу. Я знаю, что я передаю правильные верительные грамоты и прочее. Тогда мне стало понятно, что, возможно, моему тестовому проекту (который находится под тем же самым решением, что и мой реальный проект) может понадобиться собственный web.config файл со строкой подключения и т.д. Или файл конфигурации приложения может быть, так как это проект библиотеки приложений.

Итак, просто скопирую файл web.config из моего реального проекта и назову его днем? Или я должен брать только оттуда? Или я просто ушел.

Моя база данных использует пользовательскую базу данных, в которой член .net объединяется с моей базой данных. Поэтому в моем файле конфигурации мне нужно было указать ManagerProvider и roleProvider.

Вот как выглядит мой unit test

   [Test]
   public void TestVerifyUser()
   {
      AuthenticateUser authenitcate = new AuthenticateUser();
      bool vaild = authenitcate.VerifyUser("chobo3", "1234567");


      Assert.That(vaild, Is.True);
   }

Также позже у меня есть один из моих методов asp.net mvc ActionResult (точнее, для входа в систему). У меня есть это:

FormsAuthentication.RedirectFromLoginPage(loginValidation.UserName, rememberMe);

Итак, как я могу написать unit test, который будет делать то, что сделает пользователь. Скажите, что они начинаются с домашней страницы, затем нажмите на страницу входа и войдите в систему успешно. Я хочу, чтобы они перенаправлялись на домашнюю страницу.

Я не уверен, как представить это в коде. Я уверен, что RedirectFromLoginPage работает, и теперь это то, что я действительно тестирую. Я проверяю факт, что у меня есть 3 вещи, которые могут произойти в методе login ActionResult.

  • Пользователь регистрируется и отправляется туда, откуда он пришел.
  • Пользователь не может войти в систему и отправлен обратно в LoginView и видит сообщения об ошибках.
  • Пользователь попытался перейти на безопасный и был перенаправлен на страницу входа в систему. Если логин успешно будет перенаправлен обратно на защищенную страницу через ReturnUrl.

Итак, я хочу сделать тест, чтобы проверить, работают ли они так, как должны. Поэтому поэтому мне нужно, чтобы пользователь пришел с такой же домашней страницы, чтобы увидеть, будут ли они перенаправлены обратно к нему позже, и если они появятся на защищенной странице, они снова перенаправляются к этому позже.

Также я использую NUnit 2.5 и VS2008 Pro.


Это то, что я пытаюсь проверить. Я в той части, где я пытаюсь проверить, является ли пользователь действительным или нет (оператор if). Я не знаю, как это проверить.

public ActionResult Login(string returnUrl, FormCollection form, bool rememberMe)
       {
            LoginValidation loginValidation = new LoginValidation();
            try
            {
                UpdateModel(loginValidation, form.ToValueProvider());

            }
            catch
            {

                return View("Login");
            }

            if (ModelState.IsValid == true)
            {

                bool valid = authenticate.VerifyUser(loginValidation.UserName, loginValidation.Password);

                if (valid == false)
                {
                    ModelState.AddModelError("frm_Login", "Either the Password or UserName is invalid");

                }
                else if (string.IsNullOrEmpty(returnUrl) == false)
                {
                    /* if the user has been sent away from a page that requires them to login and they do 
                     * login then redirect them back to this area*/
                    return Redirect(returnUrl);
                }
                else
                {

                    FormsAuthentication.RedirectFromLoginPage(loginValidation.UserName, rememberMe);
                }

            }


            return View("Login");
        }

Ответы

Ответ 1

Вы можете протестировать свои контроллеры и большую часть своего настраиваемого поставщика, реорганизовывая свой код пользовательского членства на два уровня: репозиторий доступа к данным, который взаимодействует только с базой данных, и уровень обслуживания, который использует компоненты репозитория для предоставления API-интерфейса членства. Уровень обслуживания - это то, где вы должны проверять аргументы, удерживать и применять такие параметры, как EnablePasswordReset, и переводить любые исключения базы данных или коды состояния в форму, подходящую для потребления контроллером.

Когда вы укажете каждый слой со своим интерфейсом, потребители могут писать на этот интерфейс независимо от того, как он реализован. Когда ваше приложение работает, ваш провайдер, конечно, разговаривает с базой данных через эти интерфейсы, но для тестирования вы можете издеваться над репозиторием или интерфейсами служб. Вы можете протестировать свой уровень сервиса, высмеивая уровень хранилища, не связывая его с базой данных или файлом web.config, и вы можете проверить свои контроллеры, высмеивая уровень сервиса. Если вы не хотите реорганизовывать весь провайдер, вы можете проверить свои контроллеры, только если вы создадите интерфейс службы и используете ваши контроллеры.

Чтобы быть конкретным, если немного подробней, ваши интерфейсы репозитория и службы могут выглядеть примерно так:

namespace Domain.Abstract {
    public interface IRepository {
        string ConnectionString { get; }
    }
}

namespace Domain.Abstract {
    public interface IUserRepository : IRepository {
        MembershipUser CreateUser(Guid userId, string userName, string password, PasswordFormat passwordFormat, string passwordSalt,
                string email, string passwordQuestion, string passwordAnswer, bool isApproved,
                DateTime currentTimeUtc, bool uniqueEmail);
        MembershipUser GetUser(Guid userId, bool updateLastActivity, DateTime currentTimeUtc);
        PasswordData GetPasswordData(Guid userId, bool updateLastLoginActivity, DateTime currentTimeUtc);
        void UpdatePasswordStatus(Guid userId, bool isAuthenticated, int maxInvalidPasswordAttempts, int passwordAttemptWindow, 
                      DateTime currentTimeUtc, bool updateLastLoginActivity, DateTime lastLoginDate, DateTime lastActivityDate);
        //....
    }
}

namespace Domain.Abstract {
  public interface IUserService {
    bool EnablePasswordRetrieval { get; }
    bool EnablePasswordReset { get; }
    bool RequiresQuestionAndAnswer { get; }
    bool RequiresUniqueEmail { get; }
    //....

    MembershipUser CreateUser(string applicationName, string userName, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved);
    MembershipUser GetUser(Guid userId, bool userIsOnline);
    bool ValidateUser(Guid userId, string password);
    //...
    }
}

namespace Domain.Concrete {
  public class UserService : IUserService {
    private IUserRepository _userRepository;


    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }
    //...
    public bool ValidateUser(Guid userId, string password) {
        // validate applicationName and password here
        bool ret = false;
        try {
            PasswordData passwordData;
            ret = CheckPassword(userId, true, true, DateTime.UtcNow, out passwordData);
        }
        catch (ObjectLockedException e) {
            throw new RulesException("userName", Resource.User_AccountLockOut);
        }
        return ret;
    }

    private bool CheckPassword(Guid userId, string password, bool updateLastLoginActivityDate, bool failIfNotApproved,
                                DateTime currentTimeUtc, out PasswordData passwordData) {
        passwordData = _userRepository.GetPasswordData(userId, updateLastLoginActivityDate, currentTimeUtc);

        if (!passwordData.IsApproved && failIfNotApproved)
            return false;

        string encodedPassword = EncodePassword(password, passwordData.PasswordFormat, passwordData.PasswordSalt);
        bool isAuthenticated = passwordData.Password.Equals(encodedPassword);

        if (isAuthenticated && passwordData.FailedPasswordAttemptCount == 0 && passwordData.FailedPasswordAnswerAttemptCount == 0)
            return true;
        _userRepository.UpdatePasswordStatus(userId, isAuthenticated, _maxInvalidPasswordAttempts, _passwordAttemptWindow,
                                            currentTimeUtc, updateLastLoginActivityDate,
                                            isAuthenticated ? currentTimeUtc : passwordData.LastLoginDate,
                                            isAuthenticated ? currentTimeUtc : passwordData.LastActivityDate);

        return isAuthenticated;
    }
}

Ответ 2

Система членства Asp.Net предназначена для работы в контексте запроса Asp.Net. Итак, у вас есть три варианта.

  • Большинство людей, столкнувшихся с такой зависимостью, напишут тонкую обертку вокруг него. Обертка ничего не делает, просто перенаправляет все вызовы на базовую зависимость. Таким образом, они просто не проверяют это. Ваш AuthenticateUser является такой оболочкой. Вероятно, вы должны сделать все методы виртуальными или извлечь интерфейс, чтобы сделать его макетным, но эта другая история.
  • Используйте TypeMock Isolator и mock Membership.
  • Используйте Ivonna framework и запустите свой тест в контексте Asp.Net(это будет интеграционный тест).

Ответ 3

К сожалению, вы не можете просто скопировать ваш web.config или ваш app.config и заставить его работать таким образом. Причина в том, что ваша сборка выполняется внутри процесса NUnit, а не под вашим приложением.

Чтобы исправить ситуацию, вам, вероятно, придется выполнять Mock или Stub членов членства, которые вы вызываете, или следовать подходу Console over Configuration к настройкам, которые вы сохранили в вашем web.config.

Есть много насмешливых фреймворков, но вот пара: Rhino Mocks, Moq

Кроме того, чтобы следовать подходу, описанному в Конвенции, вы могли бы сделать что-то вроде этого:

static ConfigurationSettings
{
     static String SomeSetting
     {
        get
        {
           var result = "HARDCODEDVALUE";
           if (ConfigurationManager.AppSettings["SOMEKEY"] != null)
               result = ConfigurationManager.AppSettings["SOMEKEY"];
           return result;
     }
}

Затем вы можете использовать этот код следующим образом:

//this is how the old code might look
var mySetting = ConfigurationManager.AppSettings["SOMEKEY"];
//use the setting

//this is how the new code would look
var mySetting = ConfigurationSettings.SomeSetting;
//use the setting   

Таким образом, ваш тест будет работать, и когда вы запустите его под своим приложением, он будет использовать любые настройки конфигурации, которые вы сохранили.