Unit Testing Asp .NET Profiles. Part 3, Test-Friendly Provider

 
event

We left the story in the “how” retrieve the data from the profile system in our tests. Introducing the ProfileTestProvider.

The main responsibility of this class is performing in-memory operations during our tests, so that our properties can be retrieved and stored, as well as initialized, all of which will be performed without accessing any external storage.
Another responsibility we place on its shoulders is to ease assertions performed on the values of properties.

Not two bad, let’s see some code!

The Provider

public class ProfileTestProvider : ProfileProvider
{
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
return mergeCollections(collection, Properties);
}
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
Properties = collection;
}
// ... bunch of uninteresting methods
public SettingsPropertyValueCollection Properties { get; set; }
public void AssertPropertyValue(string propertyName, Constraint constraint)
{
SettingsPropertyValue p = Properties[propertyName];
Assert.That(p.PropertyValue, constraint);
}
public void StubValues(params KeyValuePair<string, object>[] values)
{
Properties = new SettingsPropertyValueCollection();
foreach (var pair in values)
{
Type type = pair.Value.GetType();
var settings = new SettingsProperty(pair.Key, type, null, false, defaultOf(type),
SettingsSerializeAs.ProviderSpecific, null, false, false);
var settingsValue = new SettingsPropertyValue(settings) { PropertyValue = pair.Value };
Properties.Add(settingsValue);
}
}
private static SettingsPropertyValueCollection mergeCollections(SettingsPropertyCollection noValues, SettingsPropertyValueCollection withValues)
{
SettingsPropertyValueCollection merged = new SettingsPropertyValueCollection();
foreach (SettingsProperty property in noValues)
{
var setting = new SettingsPropertyValue(property);
if (withValues != null && withValues[property.Name] != null && withValues[property.Name].PropertyValue != null)
{
setting.PropertyValue = withValues[property.Name].PropertyValue;
}
merged.Add(setting);
}
return merged;
}
private static object defaultOf(Type t)
{
if (!t.IsValueType) return null;
return Activator.CreateInstance(t);
}
}
view raw methods.cs hosted with ❤ by GitHub

Not a lot of code, but still enough to deserve some explanations:

  • GetPropertyValues(): Invoked when values from the profile need to be accessed, it merges the collection of value-less properties coming from configuration, with the externally-visible collection of properties that will (hopefully) contain some values coming from some sort of setup in the test
  • SetPropertyValues(): Invoked when the profile is saved, will replace the externally-visible collection of properties to be examined with the one that contains the current values, allowing its inspection and assertions
  • StubValues(): allows setting values to the profile properties prior to their retrieval. It will be invoked during the test “arrange” phase
  • AssertPropertyValue(): allows performing an NUnit assertion to a value of the collection. It will be invoked during the “assert” phase of the test

Reading from the Profile

Now… how would  class like this will make the Retrieve_ExistingEmail_InformationFromProfileAndAnotherDataSource() test pass? It’ll be just a matter of configuring the profile system for our test project, specifying our in-memory provider, as well as the set of properties to retrieve (in the case of the HybridClass only these two properties are needed):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<profile enabled="true" defaultProvider="testProvider">
<providers>
<clear />
<add name="testProvider" type="DgonDotNet.Blog.Samples.AspNetProfiles.ProfileTestProvider, DgonDotNet.Blog.Samples.AspNetProfiles"/>
</providers>
<properties>
<clear/>
<add name="Gender" type="System.Nullable`1[System.Byte]" serializeAs="ProviderSpecific" defaultValue="[null]" />
<add name="DayOfBirth" type="System.Nullable`1[System.DateTime]" serializeAs="ProviderSpecific" defaultValue="[null]"/>
</properties>
</profile>
</system.web>
</configuration>

And, if you believe me (or better, run the actual test in the code provided) you are going to depict a big, bright green light. But coming to review the code for the newly passed test, we found nothing of interest to exercise our new provider, it was just checking the properties coming from the other data source, not from the profile. Let’s complete the test to proves that we are indeed getting the properties from the profile system:

[Test]
public void Retrieve_ExistingEmail_InformationFromProfileAndAnotherDataSource()
{
string existingEmail = "me@here.com";
decimal fromAnotherSource = 10m;
DateTime birthDay = new DateTime(1977, 3, 11);
byte femaleRepresentation = 1;
ProfileTestProvider provider = (ProfileTestProvider)ProfileManager.Provider;
var subject = initSubject();
_another.Stub(a => a.Retrieve(existingEmail)).Return(fromAnotherSource);
provider.StubValues(
new KeyValuePair<string, object>(UserProfile.DAYOFBIRTH, birthDay),
new KeyValuePair<string, object>(UserProfile.GENDER, femaleRepresentation));
HybridClass retrieved = subject.Retrieve(existingEmail);
Assert.That(retrieved.NotFromProfile, Is.EqualTo(fromAnotherSource));
// another post subject would be how to handle temporal-dependant clases, but not this one
Assert.That(retrieved.AgeFromProfile.Value.Advent, Is.EqualTo(birthDay));
Assert.That(retrieved.GenderFromProfile.Value, Is.EqualTo(AGenderEnum.Female));
}
view raw retrieve.cs hosted with ❤ by GitHub

Again, the faith of the reader will allow me to say this test is passing.

Writing to the Profile

Armed with the confidence that the service it is actually reading information from the profile, let us test that it writes back the data to the correct data-source:

[Test]
public void Persist_InformationToProfileAndAnotherDataSource()
{
string email = "me@here.com";
DateTime birthDay = new DateTime(1977, 3, 11), today = new DateTime(2010, 4, 30);
byte maleRepresentation = 0;
AnAgeValueObject age = new AnAgeValueObject(birthDay, today);
HybridClass toBePersisted = new HybridClass(email, age, AGenderEnum.Male, 15m);
ProfileTestProvider provider = (ProfileTestProvider)ProfileManager.Provider;
ServiceClass subject = initSubject();
subject.Persist(toBePersisted);
_another.AssertWasCalled(a => a.Save(email, 15m));
provider.AssertPropertyValue(UserProfile.DAYOFBIRTH, Is.EqualTo(birthDay));
provider.AssertPropertyValue(UserProfile.GENDER, Is.EqualTo(maleRepresentation));
}
view raw persist.cs hosted with ❤ by GitHub

 

Take-Aways

I think we can take several things away from this brief series:

  • Classes that interact with the profile system via a ProfileBase instance can be easy unit tested by stubbing out the surface of contact with the profile infrastructure
  • How to include configuration into test projects
  • Interactions with the profile system can be tested by the means of a provider built for testing

So, next time you feel the need of using the profile system, do not ditch it upfront because you think it is going to be difficult to test.