coder, gamer, parent
Every now and again it’s necessary to resolve a dependency at runtime. When this happens you don’t really have much of a choice but to insert mock objects into your test fixtures IoC container. The following patterns allow me to specify 1 abstract base class and have all dependent unit tests inherit these stubs thus satisfying their dependencies.
An example class that uses a service locator pattern. Note: This will also work for constructor based dependency injection as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class CustomerLeaseTerm { public virtual Lease Lease { get; set; } public virtual Invoice Invoice { get; set; } public virtual long AdditionalDataTransfer { get; set; } // Lazy Load the Additional Data Transfer Broker using the Service Locator Pattern private IAdditionalDataTransferBroker _additionalDataTransferBroker = null; public virtual IAdditionalDataTransferBroker AdditionalDataTransferBroker { get { return _additionalDataTransferBroker ?? (_additionalDataTransferBroker = IoC.Resolve<IAdditionalDataTransferBroker>()); } } // Return a calculated cost at runtime using the Additional Data Transfer Broker public virtual decimal AdditionalDataTransferCost { get { return AdditionalDataTransferBroker .GetByUnitsInGigabytes(AdditionalDataTransfer) .EffectiveCost; } } (...) } |
So, how do we add mock objects into an IoC Container?
Because we have a class which has a dependency on a windsor container using IoC.Resolve
1. By adding the mocked instance to the IoC Container.
container.Kernel.AddComponentInstance<ITYpe>(myMockedObject);
2. By mocking the container itself using Rhino and then stubbing the Ioc.Resolve
container.Resolve<IType>();
I prefer to add the container initialization into an abstract base class, this allows multiple unit test fixtures to inherit a stubbed version of all of the IoC dependencies. In addition to that I can simply modify 1 test fixture to override InitializeStubs() to allow me to specify my mocked data for testing.
By adding these to an abstract base class this also allows me to satisfy most of Uncle Bobs SOLID priciples of OOD. See http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod for a full disclosure of who Uncle Bob is and what is SOLID.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /// <summary> /// An Abstract Base Class containing common Service Locator Objects required to be resolved from an IoC Container during runtime. /// Override InitializeStub to change the default behavior of the common objects. /// </summary> public abstract class IocFixtureBaseClass : FixtureBaseClass { protected IAdditionalDataTransferBroker AdditionalDataTransferBroker; protected IAdditionalMemoryBroker AdditionalMemoryBroker; protected IAdditionalStorageBroker AdditionalStorageBroker; protected IWindsorContainer Container; public override void SetUp() { base.SetUp(); InitializeIoC(); } protected virtual void InitializeIoC() { AdditionalStorageBroker = MockRepository.GenerateStub<IAdditionalStorageBroker>(); AdditionalMemoryBroker = MockRepository.GenerateStub<IAdditionalMemoryBroker>(); AdditionalDataTransferBroker = MockRepository.GenerateStub<IAdditionalDataTransferBroker>(); Container = MockRepository.GenerateMock<IWindsorContainer>(); Container.Stub(x => x.Resolve<IAdditionalStorageBroker>()).Return(AdditionalStorageBroker); Container.Stub(x => x.Resolve<IAdditionalMemoryBroker>()).Return(AdditionalMemoryBroker); Container.Stub(x => x.Resolve<IAdditionalDataTransferBroker>()).Return(AdditionalDataTransferBroker); InitializeStubs(); IoC.Initialize(Container); } protected virtual void InitializeStubs() { var mockAdditionalMemory = new AdditionalMemory(); var mockAdditionalStorage = new AdditionalStorage(); var mockAdditionalData = new AdditionalDataTransfer(); AdditionalMemoryBroker.Stub(x => x.GetById(0)).IgnoreArguments().Return(mockAdditionalMemory); AdditionalStorageBroker.Stub(x => x.GetById(0)).IgnoreArguments().Return(mockAdditionalStorage); AdditionalDataTransferBroker.Stub(x => x..GetById(0)).IgnoreArguments().Return(mockAdditionalData); } } |
An example test fixture would go something like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [TestFixture] public class LeaseFixture : IocFixtureBaseClass { private IExcessDataUsageCostService _costService; public override void SetUp() { base.SetUp(); _costService = new ExcessDataUsageCostService(); } public override void InitializeStubs() { var mockAdditionalMemory = new AdditionalMemory() { id = 1, CostInCents = 500, UnitsInMegabytes = 128, Text = "Mocked Memory" }; var mockAdditionalStorage = new AdditionalStorage() { id = 1, CostInCents = 1000, UnitsInMegabytes = 5000, Text = "Mocked Storage"}; var mockAdditionalData = new AdditionalDataTransfer(){ id = 1, CostInCents = 1000, UnitsInMegabytes = 5000, Text = "Mocked Data"}; AdditionalMemoryBroker.Stub(x => x.GetByUnitsInMegabytes(128)).Return(mockAdditionalMemory); AdditionalStorageBroker.Stub(x => x.GetByUnitsInGigabytes(5)).Return(mockAdditionalStorage); AdditionalDataTransferBroker.Stub(x => x.GetByUnitsInGigabytes(5)).Return(mockAdditionalData); } [Test] public void GetByIdReturnsAnObject() { var customerLease = new customerLease(); customerLease.CustomerLeaseTerm.AdditionalDataTransfer = 5; // AdditionalDataTransferCost is a calculated field at runtime based on a value per Gigabyte Assert.AreNotEqual(0, customerLease.CustomerLeaseTerm.AdditionalDataTransferCost); } } |
The preferred method for resolving runtime dependencies is by using constructor based DI. There is no real difference between the two except; Using a service locator allows for lazy object instantiation at runtime. This is super useful if you have a Dependency which cannot be resolved upon construction.
Open for comments or suggestions?
Justin is a Senior Software Engineer living in Brisbane. A Polyglot Developer proficient in multiple programming languages including [C#, C/C++, Java, Android, Ruby..]. He's currently taking an active interest in Teaching Kids to Code, Functional Programming, Robotics, 3D Printers, RC Quad-Copters and Augmented Reality.
Software Engineering is an art form, a tricky art form that takes as much raw talent as it does technical know how. I'll be posting articles on professional tips and tricks, dos and donts, and tutorials.