Använda mockningsramverk med Unity
Under senaste MSDN Live så visade jag och Mikael Deurell bl.a. hur du kan använda Dependency Injection med Unity, som är en del av Enterprise Library, för att skapa lösare kopplingar mellan olika delar av ditt system.
Vi utgick ifrån en demo-applikation som hade ett beroende till en extern webbtjänst som utförde en beräkning:
För att kunna avgöra vad som ska vara den faktiska implementationen av SliceCalculator och ServiceAgent använde vi typdefinitioner i form av interface och visade på olika sätt att med hjälp av Unity avgöra vilken konkret klass som skulle skapas i runtime. På så sätt kunde vi enkelt skapa en fejkad ServiceAgent, ett mockobjekt, när vi testade lösningen. Syftet är att undvika att vi blandar in externa beroenden som vi inte har kontroll över i vår test.
Under dag 2, när vi var i Göteborg, fick jag två intressanta frågor:
men… blir du inte beroende av Unity i din applikation och får ett hårt beroende till just det DI/IOC-ramverket?
och
om du skulle vilja använda ett mock-ramverk istället för en egenskapad fejk-, eller mock-klass, hur fungerar det med Unity?
Om vi börjar med den första frågan: nja, jo – du blir det om du gör som jag hade gjort i min demo-kod, där jag i objekt-instansieringen använde Unity-specifik kod för att skapa önskat objekt.
Det här går att undvika om vi för in ytterligare en abstraktion i form av en Object Factory i lösningen. I denna gömmer vi all DI/IOC-ramverksspecifik kod. Exakt hur komplex man vill göra en Object Factory kan naturligtvis variera, min implementation är simplast möjliga:
public class ObjectFactory
{
public static ISliceCalculator CreateSliceCalculator()
{
IUnityContainer container = GetContainer();
return container.Resolve<ISliceCalculator>();
}
private static IUnityContainer GetContainer()
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
return container;
}
}
Jag returnerar helt enkelt det som min Unity-container skapar med hjälp av Resolve-metoden.
För att göra klasserna oberoende av ramverk använder jag det som kallas för Constructor Injection för injektion av beroenden.Jag sätter också att en specifik klass ska instansieras i default-konstruktorn. På så sätt kan användaren av klassen avgöra om och hur Dependency Injection ska användas. Det här kan lösas på oerhört många olika sätt, men en poäng med just Unity är att den undersöker alla konstuktorer och ‘automagiskt’ skjuter in korrekt beroende när den skapar upp ett objekt.
Så här ser definitionen av min SliceCalculator-klass ut, alltså den klass som har ett externt beroende till min ServiceAgent:
public class SliceCalculator : ISliceCalculator
{
private IServiceAgent sa;
public SliceCalculator(IServiceAgent serviceAgent)
{
sa = serviceAgent;
}
public SliceCalculator() : this(new ServiceAgent())
{
}
...
Och för att instansiera en SliceCalculator med hjälp av min ObjectFatory:
ISliceCalculator calc = ObjectFactory.CreateSliceCalculator();
Så långt frid och fröjd. Unity skapar upp både SliceCalculator och ServiceAgent vid detta anrop - utifrån hur den är konfigurerad i app.config.
Så – till den andra frågan: om jag vill använda ett mock-ramverk med Unity – hur ser det ut i min test?
Jag testade lägga till Moq – ett lättviktsramverk för mockning av objekt som bygger på .NET 3.5 och som jag tycker verkar väldigt trivsamt. Idag på Öredev snackade jag lite med Eilon Lipton som arbetar som Dev Lead i ASP.NET MVC-teamet och det visade att de också använder Moq.
Det jag behövde göra i mitt testprojekt var:
- Ladda hem och lägga till en referens till Moq.dll i mitt testprojekt
- Skapa upp ett mock-object med Moq utifrån mitt interface IServiceAgent
- Bestäm hur objektet ska bete sig. Här är Moq väldigt straight forward och tillåter ett en-rads Lambda-uttryck för att t.ex. säga vad en metod i en viss klass ska returnera
- Registrera mock-objektet med min Unity container
Så här kan en sådan test se ut:
[TestMethod]
public void CheckNoSlicesFourPersonsLargePizza()
{
// skapa upp ett mock objekt mha Moq
Mock<IServiceAgent> mockServiceAgent = new Mock<IServiceAgent>();
mockServiceAgent.Expect(res => res.AdjustSliceIndex((int)PizzaSize.Large)).Returns(10);
// Registrera instansen hos min Unity container
container.RegisterInstance<IServiceAgent>(mockServiceAgent.Object);
ISliceCalculator calc = container.Resolve<ISliceCalculator>();
Assert.IsTrue(calc.SlicesPerPerson(PizzaSize.Large, 4) == 2);
}
Du hittar källkoden för demoapplikationen med Moq-exemplet här.