Sunday, April 12, 2009

Unit Testing a Custom Membership Provider

Wow, it's been awhile since I've blogged here. I don't want to bore you with my regret so I'll get to it. Today I was attempting to test drive a custom membership provider

    1 [Test]

    2 public void CanCreateNewUser()

    3 {

    4     TestProvider.Initialize(ProviderName, DefaultConfig);

    5     MembershipCreateStatus Status;

    6     ApplicationUser UserToSave =

    7         new ApplicationUser(CorrectUserName,

    8                             CorrectPassword,

    9                             CorrectEmail)

   10                         {

   11                             PasswordQuestion = ThePasswordQuestion,

   12                             PasswordAnswer = ThePasswordAnswer

   13                         };

   14     UserRepository.Expect(x => x.Save(UserToSave))

   15         .IgnoreArguments();

   16 

   17     MembershipUser NewUser =

   18         TestProvider.CreateUser(CorrectUserName,

   19                                 CorrectPassword,

   20                                 CorrectEmail,

   21                                 ThePasswordQuestion,

   22                                 ThePasswordAnswer,

   23                                 false,

   24                                 null,

   25                                 out Status);

   26 

   27     Assert.AreEqual(MembershipCreateStatus.Success, Status);

   28     UserRepository.VerifyAllExpectations();

   29 }



I hit run in my NUnit test runner and bam, I got this error

Tests.UnitTests.CustomMembershipProviderTestCase.CanCreateNewUser: System.ArgumentException : The membership provider name specified is invalid. Parameter name: providerName

At the top of the stack trace was the MembershipUser constructor so that's where I started my search. O how I love Reflector. I fired it up and examined the constructor for the MembershipUser class and spotted the following check

    1 if ((providerName == null) ||

    2     (Membership.Providers[providerName] == null))

    3 {

    4     throw new ArgumentException(

    5         SR.GetString("Membership_provider_name_invalid"), "providerName");

    6 }



There's the culprit : Membership.Providers[providerName] == null. So I tried setting the provider programmatically in my test, but the provider's collection is read only. I know that the provider's collection is populated according to what is in the web.config so I added the necessary section to the app.config of my test project.

    1   <system.web>

    2     <membership defaultProvider="CustomMembershipProvider">

    3       <providers>

    4         <remove name="AspNetSqlMembershipProvider"/>

    5         <add applicationName="UnitTests"

    6             name="CustomMembershipProvider"

    7             type="XXX.Infrastructure.Authentication.CustomMembershipProvider, XXX.Infrastructure"/>

    8       </providers>

    9     </membership>

   10   </system.web>



I reran my test and I saw green, but I wasn't totally satisfied. The one thing I don't like about this solution is I can't control what's in the providers collection on a test by test basis.

In the back of my mind I knew I could use reflection, but I was avoiding it because I really wanted to move on. I quickly poked around the Membership and MembershipProvderCollection classes with reflector knowing, like many things, that somebody has probably solved this problem. Within minutes I found Colin Bowern's post describing the same exact issue. The code below to programmatically populate the provider's collection is to his credit. Thanks Colin.

    1 private void InjectProvider(ProviderCollection Collection, ProviderBase Provider)

    2 {

    3     typeof(ProviderCollection).GetField("_ReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)

    4                               .SetValue(Collection, false);

    5 

    6     Hashtable Hash = (Hashtable)typeof(ProviderCollection).GetField("_Hashtable", BindingFlags.Instance | BindingFlags.NonPublic)

    7                                                           .GetValue(Collection);

    8 

    9     if (Hash[Provider.Name] == null)

   10     {

   11         Hash.Add(Provider.Name, Provider);

   12     }

   13     else

   14     {

   15         Hash[Provider.Name] = Provider;

   16     }

   17 }



Now the top two lines of my test look like

    1 TestProvider.Initialize(ProviderName, DefaultConfig);

    2 InjectProvider(Membership.Providers, TestProvider);



And my test still passes.

No comments: