Friday 7 October 2011

Wrinkles with overloaded methods when using static classes in tests

This is another followup to these posts.

When mimicking the interface of an existing class you may need to provide behaviour for a set of overloaded methods. At that point you hit a subtlety of the way we’re injecting behaviour – you can’t overload on the delegate type and retain the same delegate name.

So, ideally for some replacement File class I’d like to do this, but unfortunately I can't:

public static class File
{
    public static Action<string, string> CopyImpl = System.IO.File.Copy;

    public static Action<string, string, bool> CopyImpl = System.IO.File.Copy;

    public static void Copy(string source, string destination)
    {
        CopyImpl(source, destination);
    }

    public static void Copy(string source, string destination, bool overwrite)
    {
        CopyImpl(source, destination, overwrite);
    }
}

There are a couple of alternatives. I could rename the second delegate to something like "CopyWithOverwriteImpl", which feels very clunky:

public static class File
{
    public static Action<string, string> CopyImpl = System.IO.File.Copy;

    public static 
        Action<string, string, bool> CopyWithOverwriteImpl = System.IO.File.Copy;

    public static void Copy(string source, string destination)
    {
        CopyImpl(source, destination);
    }

    public static void Copy(string source, string destination, bool overwrite)
    {
        CopyWithOverwriteImpl(source, destination, overwrite);
    }
}

Alternatively I could implement both in terms of the operation with the broader signature.

public static class File
{
    public static Action<string, string, bool> CopyImpl = System.IO.File.Copy;

    public static void Copy(string source, string destination)
    {
        CopyImpl(source, destination, false);
    }

    public static void Copy(string source, string destination, bool overwrite)
    {
        CopyImpl(source, destination, overwrite);
    }
}

I prefer the latter approach, because the code seems simpler. However, it glosses over some detail -- what if our intent is truly to mimic System.IO.File, and the behaviour of System.IO.File.Copy changes to allow overwriting? Admittedly, this particular change would have quite widespread ramifications, but it might happen with other classes.

Realistically I think there's two steps here: the first opens up a seam in legacy code -- in which you want to replicate the existing System.IO.File behaviour. As long as you write the method to replicate this behaviour at the time you open the seam, then you're ok.

The next step comes when you start using these classes by default in new code. In that case, you get to choose the behaviour you want; you don't need to care about the behaviour of a particular overload of System.IO.File.Copy.

The latter approach seems best to me.

No comments: