Tuesday, July 06, 2010

TDD Kata for decoupling a finicky 3rd party class with Dependency Injection and Mocks

For a developer who is new to the concept of Dependency Injection, it can be difficult to see the motivation past the theory. A way to make this practical for the developer is to focus on PAIN POINTS.

One pain point which developers are increasingly encountering is the use of 3rd party DLLs for special functions (eg. for image processing or PDF manipulation), where those DLLs employ strict (finicky) licensing requirements. A typical scenario would be one license for the production server, but a limited or temporary-only license for the development box (or boxes). If a developer has coupled their code by instantiating a third party class directly within their own custom class, they may find themselves at a current or future point unable to run the code on their development machine due to a finicky, frustrating, licensing exception.

This is a very practical case for the need to decouple their code. When the developer starts to backfill legacy code like this with TDD-based unit tests, this is an opportunity for them to employ DI (dependency injection) and a mocking framework. After their test initially fails with the finicky licensing error, they can update the SUT (their custom class, the "system-under-test") to interact with an abstraction (the interface) rather than directly with the problematic third party class. They can use a mocking framework to substitute a mock implementation that will satisfy the requirements of the call to the 3rd party class.

This concept can be practiced with a TDD kata that specifically walks through the steps to achieve this. The rest of this blog post demonstrates that kata.
NOTE   The code demonstrating a completed form of this kata can be found here on github.
The following TDD kata demonstrates how to decouple a custom class that calls to a finicky 3rd party class, using Dependency Injection and mocks.

Setup
1) Create a solution with two projects:
  • Tests.Unit
  • Utils
2) Add references to NUnit and Rhino.Mocks to your Tests.Unit project.
3) This is a decoupling scenario, so you want to begin with coupled code. Therefore, create a class, FinickyThirdPartyApp with a method that throws an exception (eg. that its licensing is broken for dev machines)

public class FinickyThirdPartyApp
{
    public string DoSomethingProprietary()
    {
        throw new Exception("I'm FINICKY about licensing on dev machines! You can't use me, dev!");
    }
}

4) Now create a custom class with a single method that instantiates the third party class, and calls its proprietary method.

public class FinickyCoordinator
{
    public string DoMyCustomAction()
    {
        var finickyThirdPartyApp = new FinickyThirdPartyApp();
        return finickyThirdPartyApp.DoSomethingProprietary();
    }
}

5) You are now ready to begin.

The Kata
1) In the Tests.Unit namespace, create a class, FinickyCoordinatorTests.cs.
2) Create a test that calls DoMyCustomAction() method and asserts an expected value for the string.
3) Run the test. It will fail with the licensing exception from the third party app.
4) Now add Rhino.Mocks to the test class. Create property [Setup] and [TearDown] methods, and mock a new interface, IFinickyWrapper.

[TestFixture]
public class FinickyCoordinatorTests
{
    private MockRepository _mockRepository;
    private IFinickyWrapper _finickyWrapper;

    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository();
        _finickyWrapper = _mockRepository.StrictMock<IFinickyWrapper>();
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.VerifyAll();
    }

    // test here
}

5) In the test, change the constructor of your custom class (in this sample code, FinickyCoordinator) to accept IFinickyWrapper as an input parameter (the dependency-injected interface).

var sut = new FinickyCoordinator(_finickyWrapper);
var result = sut.DoMyCustomAction();

result.ShouldEqual(someValue);

6) Finally, begin the test by specifying the mock expectations (the call to the interface wrapper method, and the expected returned string).

[Test]
public void DoMyCustomActionMethod_NoInputParams_ReturnsExpectedString()
{
    const string someValue = "Some value returned";
    Expect.Call(_finickyWrapper.DoSomethingProprietary()).Return(someValue);

    _mockRepository.ReplayAll();

    var sut = new FinickyCoordinator(_finickyWrapper);
    var result = sut.DoMyCustomAction();

    result.ShouldEqual(someValue);
}

7) Run the test, which will fail.
8) Now correct the code within FinickyCoordinator to match the dependency injection.

public class FinickyCoordinator
{
    private readonly IFinickyWrapper _finickyWrapper;

    public FinickyCoordinator(IFinickyWrapper finickyWrapper)
    {
        _finickyWrapper = finickyWrapper;
    }

    public string DoMyCustomAction()
    {
        return _finickyWrapper.DoSomethingProprietary();
    }
}

9) Run the test, and verify that the test is passing.
10) Finally, create an implementation class which actually calls to the 3rd party class (but will no longer be called by the unit test.)
public class ThirdPartyFinickyWrapper : IFinickyWrapper
{
    public string DoSomethingProprietary()
    {
        var finickyThirdPartyApp = new FinickyThirdPartyApp();
        return finickyThirdPartyApp.DoSomethingProprietary();
    }
}

The kata is complete.

No comments: