luni, 10 mai 2010

Unit Testing With the ASP.NET Membership Provider – the Memory Membership Provider

Since it was first launched, along with the ASP.NET 2.0, the Provider Model proved its usefulness and reliability and has been widely used by various web applications, in different scenarios.

A good development practice is to have “test driven development”, or at least to write reliable unit tests for the “business logic” code and, since the web security (membership and roles) is an important part of the application’s “business logic” layer, the logical conclusion is that the Membership, Roles and Profile providers should be easily handled by unit tests and mocking frameworks (such as moq). But this is not possible, since most of the mocking frameworks require virtual methods or interfaces and the Membership Provider’s base (abstract) class doesn’t have any virtual methods. Creating wrappers for Membership provider is not the best way to go, since this adds more unwished code complexity.

Some business logic components may use the Membership Provider in this way:

public partial class
UserImport
{

public
MembershipProvider Members {get; set;}

public void
ImportUser(SomeUserData userData)
{
………
MembershipUser user = this.Members.CreateUser(username, password, email, question, answer, isApproved, providerUserKey, out status);
……..
}
}

Creating, running and maintaining a unit test for the ImportUser method implies using the ASPNETDB database, thus leading to poor testing performance.

Having this, I think the best option is to have a custom Memory Membership Provider, that may be used only for Unit Testing purposes, to easily manage users in memory without using the ASPNETDB database.

As already said, the primary objective of this Membership Provider is to easily handle the Users in memory, as properly handle the unit testing of other dependant “business logic” components.

public class
MemoryMembershipProvider : MembershipProvider
{
    public MemoryMembershipProvider()
    {
        this.Users = new List<MembershipUser>();
    }

The “Users” list is the memory data store.

Below are some possible implementations of common membership Provider methods:

public override
MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
    status = MembershipCreateStatus.Success;
    var theResult = default(MembershipUser);
    var searchForDuplicateUsername = from p in this.Users
                                     where p.UserName.ToLowerInvariant() == username.ToLowerInvariant()
                                     select p;
    if(searchForDuplicateUsername.Count() != 0)
    {
        status = MembershipCreateStatus.DuplicateUserName;
    }
    if(status == MembershipCreateStatus.Success)
    {
        theResult = new MembershipUser("MemoryProvider", username, Guid.NewGuid(), email, passwordQuestion, null, isApproved, false, DateTime.Now, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue);
    }
    return theResult;
}

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
    totalRecords = this.Users.Count;
    var theResult = new MembershipUserCollection();
    var startRowIndex = pageSize * pageIndex;
    foreach (var user in this.Users.Skip(pageSize * pageIndex).Take(pageSize))
    {
        theResult.Add(user);
    }

    return theResult;
}

public override
MembershipUser GetUser(string username, bool userIsOnline)
{
    var query = from p in this.Users
                where p.UserName.ToLowerInvariant() == username.ToLowerInvariant()
                select p;

    returnquery.SingleOrDefault();
}

public overrideMembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
    var query = from p in this.Users
                where p.ProviderUserKey == providerUserKey
                select p;

    returnquery.SingleOrDefault();
}

}

Of course, also the other methods of the Membership Provider may be custom implemented, as to properly support the unit testing and the various “business logic” scenarios.

Happy coding testing !

6 comentarii: