简体   繁体   中英

How to Unit Test Asp.net Membership?

I am new to unit testing and I am trying to test some of my .NET membership stuff I been writing.

So I am trying to check my VerifyUser method that checks if the users credentials are valid or not.

So this is what it looks like:

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

And now every time I run my unit test it fails. I know I am passing in the right credentials and stuff. Then it dawned on me that maybe my Test Project(that is under the same Solution as my real project) might need its own web.config file with the connection string and stuff. Or an app config file maybe since it is a Application Library project.

So do I just copy the web.config file from my real project and call it a day? Or should I only be taking parts from it? Or am I just way off.

My database is using a custom database with the .net membership merged with my database. So in my config file I had to specify a ManagerProvider and a roleProvider.

This is how my unit test looks like

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


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

Also later on I have in one of my asp.net mvc ActionResult Methods(the Login View to be exact) I have this:

FormsAuthentication.RedirectFromLoginPage(loginValidation.UserName, rememberMe);

So now how can I write a unit test that would do what a user would do. Say they start at the Home page then click on the login page and log in successfully. I want them to be redirect to the home page.

I am not sure how to represent that in code. I am pretty sure that the RedirectFromLoginPage works and thats now what I am really testing. I am testing the fact that I have 3 things that can happen in the login ActionResult method.

  1. User logs in and gets sent back where they came from.
  2. User fails to login and gets sent back to the LoginView and sees error messages.
  3. User has tried to go to a secure and has been redirect to the login page. If the login successfully the will be redirect back to the secure page via the ReturnUrl.

So I want to do a test to see if these work as they should. So thats why I need to have the user coming from like the home page to see if they get redirected back to it later and if they come from a secure page they get redirect back to that later on.

I am also by the way using NUnit 2.5 and VS2008 Pro.


This is what I am trying to test. I am at the part where I am trying to see if the user is valid or not(the if statement). I have no clue how to test it.

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");
        }

You can test your controllers and much of your custom provider by refactoring your custom membership code into a two layers: a data access repository that only interacts with the database, and a service layer that uses repository components to provide the Membership API. The service layer is where you would validate arguments, hold and enforce parameters like EnablePasswordReset and translate any database exceptions or status codes into a form suitable for controller consumption.

When you specify each layer with its own interface, consumers can write to that interface regardless of how its implemented. When your app is running your provider is of course talking to the database through these interfaces but for testing you can mock the repository or service interfaces. You can test your service layer by mocking the repository level without have to mess with the database or the web.config file, and you can test your controllers by mocking the service layer. If you don't want to refactor the whole provider, you can still test your controllers if you only create the service interface and have your controllers use it.

To be specific, if a little verbose, your repository and service interfaces might look something like:

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;
    }
}

The Asp.Net Membership system is designed to work in the context of an Asp.Net request. So, you have three options here.

  1. Most people when faced with such a dependency would write a thin wrapper around it. The wrapper doesn't do anything, just redirects all calls to the underlying dependency. So, they just don't test it. Your AuthenticateUser is such a wrapper. You should probably make all methods virtual or extract an interface in order to make it mockable, but that's another story.
  2. Use TypeMock Isolator and mock Membership.
  3. Use the Ivonna framework and run your test in the Asp.Net context (that would be an integration test).

Unfortunately you can't just copy your web.config or your app.config and have it work that way. The reason is that your assembly is running inside of the NUnit process, not under your application.

To remedy your situation, you're probably going to have to Mock or Stub the Membership members you're calling, or follow a Convention over Configuration approach to the settings you have stored in your web.config.

There are many mocking frameworks out there, but here are a couple: Rhino Mocks , Moq

Also, to follow the Convention over Configuration approach, you could do something like this:

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

You could then use this code like this:

//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   

This way your test will work, and when you run it under your application it will use whatever configuration settings you've stored.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM