Testing a Membership Provider
Here’s something that I was toying with yesterday: creating a MembershipProvider that does all it’s storage in-memory. This would be used in "test-only" situations where you can’t run against a "real" provider. Should be easy, right? I mean, this provider barely does anything. Instead, we end up needing various source views, a few different membership classes, and some reflection.
First Task: MembershipProvider.CreateUser
There is some logic involved in maintaining the in-memory information, and I certainly don’t want to have to run a web application to test the functionality as I’m building it, so the first thing is to create a ".Test" assembly.
My first test for creating a MembershipUser looked something like this:
[Test] public void CreateUser() { // this is what we're testing: var provider = new VirtualMembershipProvider(); // creation of all of these args omitted for brevity.. var user = provider.CreateUser(...); // verify that the MembershipUser is valid.. Assert.AreEqual(user.UserName, userName); // etc.etc.etc. }
And the implementation of VirtualMembershipProvider.CreateUser(): (obviously I’m leaving out some unimportant details..)
public override MembershipUser CreateUser(...) { // MembershipUser takes crazy big number of args.. MembershipUser user = new MembershipUser(...); // TODO: put that user object somewhere for later access.. status = MembershipCreateStatus.Success; return user; }
Can’t get much simpler, right?
First Issue – Arbitrary Dependency
Running the test gives an exception from the MembershipUser constructor: "System.ArgumentException : The membership provider name specified is invalid." So, we jump over to Reflector to see what’s going on:
if ((providerName == null) || (Membership.Providers[providerName] == null)) { throw new ArgumentException(...); }
MembershipUser has a dependency on Membership.Providers. This is because MembershipUser is not just a data container, but breaks the Single Responsibility Principle (SRP) and has methods like Update() as well. But, whatever. Looks like all I’ve got to do is add my VirtualMembershipProvider into that Membership.Providers collection, and we’ll be set.
Next Issue – Arbitrary ReadOnly-ness
I put "Membership.Providers.Add()" into the test, to see what happens. Running the test gives me a new exception: in ProviderCollection.Add() – "System.NotSupportedException : Collection is read-only." Argh! Back to Reflector! Here’s what ProviderCollection.Add() looks like (more or less):
public virtual void Add(ProviderBase provider) { if (this._ReadOnly) throw new NotSupportedException(...); // some other guard clauses... this._Hashtable.Add(provider.Name, provider); }
So all I’ve got to do is call ".IsReadOnly = false" and… ha ha jokes on me. ProviderCollection has a SetReadOnly() method, but no public way to switch readonly-ness off. Presumably there’s some really good reason for making things difficult with this totally artificial wall that they put in our path.
So, we fall back to the tool of last resort: reflection to get that private _ReadOnly field and set it to false. Now we can register our provider into the ProviderCollection. But, what an ugly hack, for seemingly no reason.
One more thing lacking: before the ProviderCollection will accept a Provider, it checks to see if the Provider has been initialized properly. So we need a call to provider.Initialize() which sets some internal state on the provider.
(Note: by this point, we actually have a fair amount of initialization code here, and its getting complicated. So this code was moved into its own class & tests – I’ve left that part out to be more concise.)
More or less, we’ve got this code to set up our MembershipProvider, outside of the official procedures:
public static void Initialize(MembershipProvider provider, string providerName) { provider.Initialize(providerName, null); // grabs a cached FieldInfo instance and sets it appropriately. // note this would affect all Membershp providers... AllowNewProviders = true; Membership.Providers.Add(provider); }
And now, our CreateUser() test passes and we can get back to work on the REAL problem.
Conclusions
- Don’t put arbitrary roadblocks in your framework. You have no idea how somebody will need to use (and abuse) it.
- Strive to keep your code decoupled. Always a good idea no matter what you’re working on.
- This is "running with scissors:" I wouldn’t propose subverting the Provider model with this sort of hackery for code that you wanted to use in production, at least not with much more testing and consideration. There might be (and probably is) a really good reason for that _ReadOnly flag, and we just broke it..
- And to bring us back to the first point: framework developers should provide the "default safe" implementation with that _ReadOnly flag, but then at least provide a way to cleanly get around the safety mechanism, for those of us who are willing to accept the risk.
So, ASP.NET, thanks for the offer of holding my hand through every step of the way, but instead how about you just get out of my way and let me get my work done, kthxbai.


