Unit testing private methods

by Mikael Henriksson 8. January 2010 12:54

Sometimes I can’t be bothered about testing the whole shebang. I just want to test what really counts without having to go through mocking external calls just to verify that something returns true.

This is probably not how one should test code but what the helicopter it works.

[Test]
public void Testing_a_private_method()
{
    BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Instance;
    var instance = new SomeInstance();
    Type type = typeof(SomeInstance);
    var para = new SomeParameterClass()
    para.SomeProperty1 = 2;
    para.SomeProperty2 = 1;
    var parameters = new object[] { para };

    MethodInfo method = type.GetMethod("InstanceMethodName", Flags);
    object obj = method.Invoke(instance, parameters);
    Assert.AreEqual(true, Convert.ToBoolean(obj));
}

EDIT: Inspired by Jan Van Ryswyck I decided to add a much cleaner way of doing this using Func<TInput, TOutput>. I recommend you read his post.

[Test]
public void Testing_a_private_method()
{
    var instance = new SomeInstance();
    var para = new SomeParameterClass()
    para.SomeProperty1 = 2;
    para.SomeProperty2 = 1;
    
    var instanceMethodName = (Func< SomeParameterClass, bool>)
    Delegate.CreateDelegate(
		typeof(Func< SomeParameterClass, bool>), instance, "InstanceMethodName"
		);
    Assert.IsTrue(instanceMethodName(para), "This should really return true");
}

Like Jan mentions it does only work for instance methods not static ones but you shouldn’t be creating too many static methods anyway :)

EDIT2: Also worth noting is that Func of course only works for methods that actually return something. If you need to check the values of something that should have been manipulated without the method returning the manipulated value we need to use another Delegate, say hello to Action!

[Test]
public void Testing_a_private_method()
{
    var instance = new SomeInstance();
    var para = new SomeParameterClass()
    para.SomeProperty1 = 2;
    para.SomeProperty2 = 1;
    
    var instanceMethodName = (Action< SomeParameterClass, bool>)
    Delegate.CreateDelegate(
		typeof(Action< SomeParameterClass, bool>), instance, "InstanceMethodName"
		);
    instanceMethodName(para);
    Assert.AreEqual(2, para.SomeProperty2, "Since we set SomeProperty2 to initial value 1 this will throw if it's not been set while InstanceMethodName executed!");
}

Tags:

Testing

An even better way of handling a singleton WURFL in asp.net

by Mikael Henriksson 4. January 2010 20:28

As soon as I find a new cool tool I try to find ways of using it. I have been using StructureMap for a while and love it. Certainly so now that some of the old crap get’s cleaned up for version 3.0 (big thanks to Jeremy for taking the time during holidays). Ideally since it’s a 13 MB file to hold in memory I prefer to have only one instance running and my attempt on a singleton sucked. Initially I did some locking and stuff but when running locally I often found myself running out of ram so it didn’t work and it was slow to initialize (startup time was around a minute). After refactoring “a little” and letting StructureMap handle the instance for me it starts up in less than a second which means I can have IIS refresh the application pool regularly (I find it best to do this when handling really big files in memory) and I am confident someone like Jeremy D Miller writes better code than I do (for now).

This example can of course be refined a lot. It consists of just a few classes. One data class used to hold the xml document, one loader class used to prepare the data we need from the xml document and one navigator class used to search in the data class and one class that implements the below interface and acts as a wrapper for the other two classes.

Could I have some code please?

 

public interface IDetection
{
  string UserAgent { get; set; }
  Handset GetAllInformation(string userAgent);
  string GetBrowserAndVersion();
  string GetCharSet();
  string GetContentType();
  string GetManufacturer();
  string GetModel();
  string GetPlatform();
  string GetScreenSize();
  string GetUAProfile();
  bool IsMobileDevice();
}

 

Note: The Handset class is just a value object used to temporarily hold information. 

The only reason this interface exists is because I want to separate patches from devices. The patches contains information about non-mobile devices like bots and computers and the implementing classes are pretty dumb so I’ll save you the read copy/paste on those.

public interface IDeviceData
{
  SortedList<string, IDictionary<string, string>> DevicesAndCapabilities { get; }
  IDictionary<string, string> DevicesAndFallback { get; }
  IDictionary<string, string> UserAgentsAndDevices { get; }
}	

Now comes one of the main classes I removed all patch loading from this one to save some space. It’s somewhat optimized and I at least like the size of the methods. Managed to get them down to a decent number of lines.

///<summary>
/// Holds mobile _wurfl information from the _wurfl.xml
///</summary>
public class DeviceDataLoader
{
    private const int AproximateNumberOfDevices = 600;
    private static readonly Stopwatch Sw = new Stopwatch();
    private bool _disposed;

    public DeviceDataLoader()
    {
        CapabilityNames = new ArrayList();
        DeviceData = new DeviceData(AproximateNumberOfDevices);
        Patches = new Patches(40);
        CompactAgents = new Dictionary<string, string>();
    }

    ///<summary>
    /// Returns true if the Wurfl document is loaded into memory
    ///</summary>
    public bool IsLoaded { get; private set; }
    public IDeviceData DeviceData { get; private set; }
    public IDeviceData Patches { get; private set; }
    public IDictionary<string, string> CompactAgents { get; set; }
    public ArrayList CapabilityNames { get; private set; }

    /// <exception cref="FileNotFoundException"><c>FileNotFoundException</c>.</exception>
    public IDeviceData Load(string xmlPath)
    {
        if (!File.Exists(xmlPath))
        {
            throw new FileNotFoundException(string.Format(CultureInfo.InvariantCulture,
                                                          "The wurfl database was not located in : {0}", xmlPath));
        }

        PrepareXmlDatabaseForusage(xmlPath);
        IsLoaded = true;
        return DeviceData;
    }

    private void PrepareXmlDatabaseForusage(string xmlPath)
    {
        try
        {
            if (!IsLoaded)
            {
                XDocument wurlf = XDocument.Load(xmlPath);
                GetMobileDevices(wurlf.Descendants());
                SetInitialCapabilities();
            }
        }
        catch (Exception ex)
        {
            LogManager.GetLogger("Providers").Error(ex);
        }
    }

    private void GetMobileDevices(IEnumerable<XElement> xElements)
    {
        foreach (XElement xElement in xElements)
        {
            if (xElement.Name == "device")
            {
                string deviceId = GetDeviceId(xElement);
                string userAgent = GetUserAgent(xElement);
                IDictionary<string, string> capabilities = GetCapabilities(xElement);

                AddMobileDevicesCapabilities(deviceId, userAgent, capabilities);
                AddMobileUserAgentsAndDeviceIds(deviceId, userAgent);
                AddMobileDevicesAndFallbacks(xElement, deviceId);

                capabilities.Clear();
            }
        }
    }

    private void GetPatchDevices(IEnumerable<XElement> xElements)
    {
        foreach (XElement xElement in xElements)
        {
            if (xElement.Name == "device")
            {
                string deviceId = GetDeviceId(xElement);
                string userAgent = GetUserAgent(xElement);
                IDictionary<string, string> capabilities = GetCapabilities(xElement);

                AddPatchDevicesCapabilities(deviceId, userAgent, capabilities);
                AddPatchUserAgentsAndDevices(deviceId, userAgent);
                AddPatchDevicesAndFallbacks(xElement, deviceId);

                capabilities.Clear();
            }
        }
    }

    private void AddMobileDevicesAndFallbacks(XElement xElement, string deviceId)
    {
        string value;
        if (!DeviceData.DevicesAndFallback.TryGetValue(deviceId, out value))
        {
            XAttribute x = xElement.Attribute("fall_back");
            if (x != null)
            {
                DeviceData.DevicesAndFallback.Add(deviceId, x.Value);
            }
        }
    }

    private void AddMobileUserAgentsAndDeviceIds(string deviceId, string userAgent)
    {
        string value;
        if (!DeviceData.UserAgentsAndDevices.TryGetValue(userAgent, out value))
        {
            DeviceData.UserAgentsAndDevices.Add(userAgent, deviceId);
        }
    }

    private void AddMobileDevicesCapabilities(string deviceId, string userAgent,
                                              IDictionary<string, string> capabilities)
    {
        string value;
        if (!string.IsNullOrEmpty(deviceId))
        {
            if (!string.IsNullOrEmpty(userAgent) && !DeviceData.DevicesAndCapabilities.ContainsKey(deviceId))
            {
                DeviceData.DevicesAndCapabilities.Add(deviceId, new Dictionary<string, string>(capabilities));
            }
        }
    }

    private static string GetDeviceId(XElement xElement)
    {
        string deviceId = string.Empty;
        if (xElement.Name == "device")
        {
            deviceId = xElement.GetAttributeValue("id");
        }

        return deviceId;
    }

    private static string GetUserAgent(XElement xElement)
    {
        string userAgent = xElement.GetAttributeValue("user_agent") ?? string.Empty;

        if (string.IsNullOrEmpty(userAgent))
        {
            userAgent = "generic";
        }
        return userAgent;
    }

    private static IDictionary<string, string> GetCapabilities(XContainer xElement)
    {
        IDictionary<string, string> capabilities = new Dictionary<string, string>();
        foreach (XElement groups in xElement.Descendants("group"))
        {
            foreach (XElement capability in groups.Descendants("capability"))
            {
                string value;
                if (!capabilities.TryGetValue(capability.GetAttributeValue("name"), out value))
                {
                    capabilities.Add(capability.GetAttributeValue("name"), capability.GetAttributeValue("value"));
                }
            }
        }

        return capabilities;
    }

    private void SetInitialCapabilities()
    {
        DeviceData.DevicesAndCapabilities.TrimExcess();
        IDictionary<string, string> tmp = DeviceData.DevicesAndCapabilities["generic"];
        foreach (string key in tmp.Keys)
        {
            CapabilityNames.Add(key);
        }
        CapabilityNames.Sort();
    }
}


Lastly I created a detection class implementing the first interface. This will be the one instance in the IoC container of choice (mine is StructureMap).  

 

public class WurflDetection : IDetection
{
    private DataNavigator _device;
    public IDeviceData DeviceData { get; private set; }
    private DeviceDataLoader _loader;

    public void Initialize(string pathToXml)
    {
        if (string.IsNullOrEmpty(pathToXml))
        {
            throw new FileNotFoundException("PathToXml :", PathToXml);
        }

        if (_loader == null)
        {
            _loader = new DeviceDataLoader();
        }

        if (!_loader.IsLoaded)
        {
            DeviceData = _loader.Load(PathToXml);
            _device = new DataNavigator(DeviceData.Devices, DeviceData.Patches, DeviceData.CapabilityNames);
        }
    }
}

 

So I add a StructureMap Registry like follows:

 

public class WurflRegistry : Registry
{
  public WurflRegistry()
  {
    IDetectionProvider provider = new DeviceProviderOne(HostingEnvironment.MapPath("/App_Data/wurfl.xml"));

      ForSingletonOf<IDetectionProvider>()
          .Use(provider);
  }
}


The really nice thing about the whole registry approach is that I don’t necessarily need any project or dll references other than to StructureMap I can let the tool scan the assemblies in the output directory for me which is sometimes the only solution visible solutions to some problems. 

 

Tags: ,

IoC | wurfl