Several Patterns for Refactoring Static Classes for Testability

Static classes and methods can be tricky business for unit tests, and I’ve had to do a lot of refactoring them lately to get some tests going. Here are a few different ways to make your static classes test-friendly.

For the examples below, assume we need to refactor the following class to make it more testable:

public static class Foo
{
    public static void Bar()
    {
        // do something
    }
}

Convert to Instance Class

A simple and straightforward solution is to convert your static class to an instance class. If you need to prevent multiple instances from existing, you can implement the singleton pattern in your class. It’s easy to do this, but it can be risky or tedious to implement across a solution depending on how much your static class is being used. For example, if your class is being accessed hundreds or thousands of times across many different projects, you may not want to do this because you’ll have to update each and every caller.

public class Foo : IFoo
{
    private static IFoo Instance { get; set; }
    
    static Foo()
    {
        Instance = new Foo();
    }
    
    private Foo()
    {
    }
    
    public void Bar()
    {
        // do something
    }
}

Keep Static Class, Extract Implementation #1

If you want or need to keep your static classes/methods as static without having to change every caller, you can extract the implementation of your static class to a new instance class and adjust the static class to use that. The implementation class uses the singleton pattern to expose a single static instance that will be used by the static class.

public static class Foo
{
    public static void Bar()
    {
        FooImplementation.Instance.Bar();
    }
}

internal class FooImplementation : IFoo
{
    public static IFoo Instance { get; private set; }
    
    static FooImplementation()
    {
        Instance = new FooImplementation();
    }
    
    private FooImplementation()
    {
    }
    
    public void Bar()
    {
        // do something
    }
}

public interface IFoo
{
    void Bar();
}

This works great, but it does require unit test authors to have knowledge of the new implementation class. This strikes me as slightly non-intuitive for developers since you go to class B to mock the behavior of class A. And that brings us to our next option…

Keep Static Class, Extract Implementation #2

Another option is to tweak the above strategy slightly so that the static property is added to the static class instead of on the implementation instance class. The nice thing about this approach is that you don’t need to have any awareness of the default implementation when providing the static class with an alternate implementation.

public static class Foo
{
    private static IFoo Implementation { get; set; }
    
    static Foo()
    {
        Implementation = new FooImplementation();
    }
    
    public static void Bar()
    {
        Implementation.Bar();
    }
}

internal class FooImplementation : IFoo
{
    public void Bar()
    {
        // do something
    }
}

public interface IFoo
{
    void Bar();
}

Create a Wrapper

Another option is to create a wrapper. Much like the first option presented, this approach has the downside of needing to update all callers to use the wrapper. However, if you are unable to modify the contents of your static class, this may be your best bet.

public static class Foo
{
    public staic void Bar()
    {
        // do something
    }
}

public class FooWrapper : IFoo
{
    private static IFoo Instance { get; set; }
    
    static FooWrapper()
    {
        Instance = new FooWrapper();
    }
    
    private FooWrapper()
    {
    }
    
    public void Bar()
    {
        Foo.Bar();
    }
}

interface IFoo
{
    void Bar();
}
Advertisements

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s