Unit testing calls to complex extension methods

Datetime:2016-08-23 02:49:26          Topic: Unit Testing           Share

This article isn’t about unit testing an extension method. That is pretty straight forward. This article is about unit testing and object that calls extension method where the extension method is difficult to test because it touches an external system, such as a database or a remote web service.

If you have an extension method that is simple and doesn’t touch and external system, it is easy to unit test. Look at the example below. There is nothing blocking you from Unit Testing code that calls this method.

public static int Add(this int left, int right) 
{
    return left + right;
}

Now image the extension method is more complex, say for a shopping cart.

public static void PlaceOrder(this Order order) 
{
    SaveToDb(Order);
    ChargeCreditCard(Order.CreditCardDetails);
}

How are you going to unit test code that calls an extension method that place an order and charges a customer’s Credit Card. Yikes. That is little harder to Unit Test, right?

How to Unit Test a call to an complex extension method

Imagine you have the following code:

  1. An object you are test called ObjectUnderTest
    public class ObjectUnderTest
    {
        private void MyObject = new MyObject();
    
        public object SomeFunction() 
        {
            return myObj.DoWork(val);
        }
    }
  2. An dependent object MyObject : IMyObject
    public class MyObject : IMyObject
    {
     // ... some code
    }
  3. An extension method on IMyObject: DoWork(this IMyObject obj, string value).
    public static object DoWork(this IMyOject obj, string value)
    {
        // really complex stuff and touches external systems
    }

You need Unit Tests for SomeFunction(). But SomeFunction has two dependencies:

  1. MyObject
  2. DoWork

The Unit Test should definitly not call the real DoWork that does really complex stuff and touches external systems.

One option to resolve this is to use dependency injection. Dependency Injection (DI) simply means that any dependencies can be injected. When some people hear DI, they think they immediately need the huge overhead of an IoC Container. IoC containers are nice and have their uses. But using an IoC container only to allow unit tests substitute a dependency  is a huge overkill. If your project already has an IoC container, feel free to use it. Otherwise, I recommend you use a simpler option. I prefer a lazy replaceable property.

Creating a Lazy Injectable Property

A lazy property just means a property that is instantiated on first use if it is null. Here is the syntax:

internal IMyObject MyObjectProperty
    {
        get { return _MyObject ?? (_MyObject = new MyObject()); }
        set { _MyObject= value; }
    } private List<object> _MyObject;

Look how simple the above code is. If _MyObject is null, the first time MyObjectProperty is called, it is instantiated to a new MyObject().It is internal because only the unit test will every replace it. I don’t really want this property exposed elsewhere. We can use InternalsVisibleTo to allow the Unit Tests access.Now my ObjectUnderTest will look like this:

public class ObjectUnderTest
{
    internal IMyObject MyObjectProperty
    {
        get { return _MyObject ?? (_MyObject = new MyObject()); }
        set { _MyObject= value; }
    } private IMyObject _MyObject;

    public object SomeFunction()
    {
        return MyObjectProperty.DoWork(val);
    }
}

Now, in the unit test, the MyObjectProperty can be replaced with a mock IMyObject.

[TestMethod]
public void SomeFunctionTest()
{    // Arrange
    var mockMyObject = new Mock<IMyObject>();
    var objUnderTest = new ObjectUnderTest();
    objectUnderTest.MyObjectProperty = mockMyObject.Object;

    // More to come . . .
}

However, it is questionable whether this is even necessary. Does MyObject do anything that requires this level of abstraction? Not in this example. It isn’t the object itself that is complex, it is the extension method that really needs to be injectable.

Creating a Lazy Injectable Property for a method

You might be asking yourself, “What about the extension method? It is a method not an object. How can I inject that?” Well, you can. Remember, even methods can be treated as objects. The answer doesn’t change much. The only difference is understanding how to treat a method as an object.You can objectify methods using multiple objects such as Action, Func, Predicate, etc. I am not going to go into how to do that here beyond the minimal needed to accomplish this task. Use Action for void, Func

Here are the steps:

  1. Create the following Lazy Injectable Property inside ObjectUnderTest:
    internal Action<MyObject, string> DoWorkMethod
        {
            [ExcludeFromCodeCoverage]
            get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
            set { _DoWorkMethod = value; }
        } private Action<MyObject, string> _DoWorkMethod;
  2. Change SomeFunction() to run the method via the Action object instead of running the method directly.
    public object SomeFunction()
         {
            return DoWorkMethod.Invoke(myObj, val);
        }
  3. In your Unit Test, you can create your ObjectUnderTest. Then you can swap out the DoWork method object.
    [TestMethod]
    public void SomeFunctionTest()
    {
        // Arrange
        var mockMyObject = new Mock<IMyObject>();
        var objUnderTest = new ObjectUnderTest();
        objectUnderTest.MyObjectProperty = mockMyObject.Object;
        bool methodWasCalled = false;
        objectUnderTest.DoWorkMethod = (obj, val) => { methodWasCalled = true; }
    
        // Act
        var result = objUnderTest.SomeFunction();
    
        // Assert
        Assert.IsTrue(methodWasCalled)
    }

You are now 100% covered. The only code we can’t cover is the lambda call to obj.DoWork because we can’t Unit Test that as it touches an external system. Which is why we marked it with the ExcludeFromCodeCoverageAttribute.





About List