Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

AutoMocking Container

, 1 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
At work I am lucky enough to work with a few bright chaps (sadly some of them are a lot youger than me, which is making me question my existence :-( , but on the other hand it is always good to learn new things). One of those new things for me happened the other […]

At work I am lucky enough to work with a few bright chaps (sadly some of them are a lot youger than me, which is making me question my existence Frown | :-( , but on the other hand it is always good to learn new things).

One of those new things for me happened the other day, where this dude at work showed me MSpec for TDD/BDD, and I have to say it was pretty awesome. The thing that I liked the most was the “AutoMocking” feature

So what is this “AutoMocking” that I am talking about, and why is it cool? Well quite simply it eliviates the brittle relationship with code and tests in their infancy, and allows tests to create constructor injection parameters automatically

I should point out that this pattern really suits new code bases, that may still be a bit in flux, it is not really that much use for well established code bases that have been around and settled for a while

It is also worth noting that this pattern can be used with code that doesn’t use any IOC/dependency injection at all, it is purely a testing concern

So what is it all about?

Well say you have this class

public class Autobooker
{
    private readonly ILogger logger;
    private readonly IRateProvider rateProvider;
    private readonly IBooker booker;

    public Autobooker(ILogger logger, IRateProvider rateProvider, IBooker booker)
    {
        this.logger = logger;
        this.rateProvider = rateProvider;
        this.booker = booker;
    }


    public void BookDeal(string ccy1, string ccy2, decimal amount)
    {
        logger.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2));
        decimal rate = rateProvider.GetRate(string.Join("", ccy1, ccy2));
        booker.Book(ccy1, ccy2, rate, amount);

    }
}

Which you may write a test case for something like this (I am using NUnit and Moq, but you may prefer you own tools to these)

[TestFixture]
public class TestCases
{
    private IWindsorContainer container;

    [SetUp]
    public void SetUp()
    {
        container = new WindsorContainer();
        container.Install(new BookerInstaller());
    }

    [TestCase("EUR", "GBP", 1500)]
    [TestCase("GBP", "EUR", 2200)]
    public void TypicalTestCase(string ccy1, string ccy2, decimal amount)
    {
        //setup
        string ccyPair = string.Join("", ccy1, ccy2);
        var rate = 1;
        var loggerMock = new Mock<ILogger>();
        var rateProviderMock = new Mock<IRateProvider>();
        var bookerMock = new Mock<IBooker>();

        rateProviderMock
            .Setup(x => x.GetRate(ccyPair)).Returns(1);

        var autobooker = new Autobooker(loggerMock.Object, rateProviderMock.Object, bookerMock.Object);
        autobooker.BookDeal(ccy1, ccy2, amount);

        //assert
        loggerMock
            .Verify(x => x.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2)),
                Times.Exactly(1));

        rateProviderMock
            .Verify(x => x.GetRate(string.Join("", ccy1, ccy2)), Times.Exactly(rate));

        bookerMock
            .Verify(x => x.Book(ccy1, ccy2, rate, amount), Times.Exactly(1));
    }
}

The problem with this code is that it is extremely coupled to the design. What would happen if the Autobooker class needed to take a new constructor dependency say something like this

public class Autobooker
{
    private readonly ILogger logger;
    private readonly IRateProvider rateProvider;
    private readonly IBooker booker;
    private readonly IPricer pricer;

    public Autobooker(ILogger logger, IRateProvider rateProvider, IBooker booker, IPricer pricer)
    {
        this.logger = logger;
        this.rateProvider = rateProvider;
        this.booker = booker;
        this.pricer = pricer;
    }
}

This should immediately ring alarm bells that your existing test cases will now break, but what can we do about it? This is where “AutoMocking” can help. Let us take a look at this shall we.

So the first step is to decide on a nice IOC container that you think is fit for the job. For me this is Castle Windsor. So that is that decision made, so what do we need to do now that we have made that decision? Well all we really need to do is get it to automatically create our Mocks for us. SO what does that look like? Well for me it looks like this

public class AutoMoqServiceResolver : ISubDependencyResolver
{
    private IKernel kernel;

    public AutoMoqServiceResolver(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public bool CanResolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        return dependency.TargetType.IsInterface;
    }

    public object Resolve(
        CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model,
        DependencyModel dependency)
    {
        var mock = typeof(Mock<>).MakeGenericType(dependency.TargetType);
        return ((Mock)kernel.Resolve(mock)).Object;
    }
}

The next step is to create some sort of IOC registration for the system under test (SOT), which for me means the Autobooker type. So we do that as follows

public class BookerInstaller : IWindsorInstaller
{
    public void Install(
        IWindsorContainer container,
        IConfigurationStore store)
    {
        container.Kernel.Resolver.AddSubResolver(new AutoMoqServiceResolver(container.Kernel));
        container.Register(Component.For(typeof(Mock<>)));

        container.Register(Classes
            .FromAssemblyContaining<Booker.Autobooker>()
            .Pick()
            .WithServiceSelf()
            .LifestyleTransient());
    }
}

With that all in place I can now write non brittle code, that will get the object I want to test from the IOC container, where all its dependencies will try to be satified by the automocking IOC container. So my test case now looks like this

[TestCase("EUR", "GBP", 1500)]
[TestCase("GBP", "EUR", 2200)]
public void TestBooking(string ccy1, string ccy2, decimal amount)
{
    var autobooker = container.Resolve<Autobooker>();
    string ccyPair = string.Join("", ccy1, ccy2);
    var rate = 1;
            
    //arrange
    container.Resolve<Mock<IRateProvider>>()
        .Setup(x => x.GetRate(ccyPair)).Returns(1);

    autobooker.BookDeal(ccy1,ccy2,amount);

    //assert
    container.Resolve<Mock<ILogger>>()
        .Verify(x => x.Log(string.Format("Booked deal for {0}/{1}", ccy1, ccy2)),
            Times.Exactly(1));

    container.Resolve<Mock<IRateProvider>>()
        .Verify(x => x.GetRate(string.Join("", ccy1, ccy2)), Times.Exactly(rate));
            
    container.Resolve<Mock<IBooker>>()
        .Verify(x => x.Book(ccy1,ccy2,rate,amount), Times.Exactly(1));

}

See how I no longer need to declare the mocks, I just define their behavior, which I think is cool


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 1 May 2014
Article Copyright 2014 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid