In this post I am going to show you how I am using StructureMap Registry along with FakeItEasy to make the setup phase of my unit tests easier. By no means I am claim that this is how it should be done but I feel it can be done this way. If you find any issues or it could be done better please let me know in the comments below. I will be happy to correct it and respond. In this post I am assuming you know about StructureMap, Dependency Injection, FakeItEasy and Unit Testing in general.
First about the problem I was facing and I am sure you must have faced it too and already solved it some other way. My system under test in this example is about an OrderingService which is responsible for placing orders, checking if inventory is there in warehouse or not and notify via email and log messages to database. Below is the code for OrderingService. We are not interested in the details for the PlaceOrder function here.
public interface IOrderingService
{
bool PlaceOrder(Order order);
}
public class OrderingService : IOrderingService
{
private IPaymentService _paymentService;
private IWarehouseService _warehouseService;
private ILoggingService _loggingService;
private IEmailService _emailService;
public OrderingService(
IPaymentService paymentService,
IWarehouseService warehouseService,
ILoggingService loggingService,
IEmailService emailService)
{
_paymentService = paymentService;
_warehouseService = warehouseService;
_loggingService = loggingService;
_emailService = emailService;
}
public bool PlaceOrder(Order order)
{
throw new NotImplementedException();
}
}
Like any system one system depends on the other and we now have a graph of dependency. For example, LoggingService depends upon IDataManager and EmailService depends upon ISmtpService.
public interface ILoggingService
{
void Log(string logText);
}
public class LoggingService : ILoggingService
{
private IDataManager _dataManager;
public LoggingService(IDataManager dataManager)
{
_dataManager = dataManager;
}
public void Log(string logText)
{
throw new NotImplementedException();
}
}
I am not showing code for EmailService for brevity. With such graph my setup phase of the unit tests started to look like following.
[TestClass]
public class BadOrderingServiceTests
{
//All Fakes to be injected.
private IPaymentService _paymentService;
private IEmailService _emailService;
private ILoggingService _loggingService;
private IWarehouseService _warehouseService;
//SUT
private IOrderingService _orderingService;
[TestInitialize]
public void Setup()
{
var smtpService = A.Fake<SmtpService>(
x => x.WithArgumentsForConstructor(
() => new SmtpService("Fake Smtp Server")));
_emailService = A.Fake<EmailService>(
x => x.WithArgumentsForConstructor(
() => new EmailService(smtpService)));
var dataManager = A.Fake<DataManager>(
x => x.WithArgumentsForConstructor(
() => new DataManager("Connection String")));
_loggingService = A.Fake<LoggingService>(
x => x.WithArgumentsForConstructor(
() => new LoggingService(dataManager)));
_paymentService = A.Fake<IPaymentService>();
_warehouseService = A.Fake<IWarehouseService>();
//SUT
_orderingService = new OrderingService(_paymentService,_warehouseService,_loggingService,_emailService);
}
[TestMethod]
public void PlaceOrder_OnPassingNonNullOrder_ShouldPlaceOrderSuccessfully()
{
//Arrange
var fixture = new Fixture();
var order = fixture.Create<Order>();
//Act
var result = _orderingService.PlaceOrder(order);
//Assert
Assert.IsTrue(result);
}
}
So you see in the setup method how much ceremony it requires to test this ordering service and if you have to do this again for another service then again all the dependencies have to be hooked in like this. Now for simplicity the paymentService and warehouseservice doesn’t take in any dependency so it is just a straight A.Fake<IPaymentService>(). This is all handy dandy for testing one service. But this isn’t ideal if you want to test another service which may require similar setup. And another thing I kept reading everywhere is writing unit tests should be fast and easy and the setup should feel just like how you wrote your system under test. Clearly the setup phase is way different from how we wrote the OrderingService above. You may ask how it is different? In the constructor of OrderingService, do you have to worry where LoggingService is getting its IDataManager from or EmailService getting its ISmtpServer? No right. Because it is being taken care by StructureMap registry like below.
public class StructureMapRegistry : Registry
{
public StructureMapRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.SingleImplementationsOfInterface();
});
For<IDataManager>().Singleton().Use<DataManager>()
.Ctor<string>("connectionString")
.Is("Production Connection String");
For<ISmtpService>().Singleton().Use<SmtpService>()
.Ctor<string>("smtpServer")
.Is("Production Smtp Server");
}
}
So you may ask why are you writing your setup that way. One reason is that I didn’t knew any other way and I learnt stuff harder way using google. For now lets focus on a light bulb moment that inspired me to make my code better. And here is a better way according to me. Just like how StructureMap took care for me the registration of all dependencies [I love you StructureMap] of OrderingService why not StructureMap take of registering all the Fake dependencies [light blub]. Let’s create a different Registry which we will call FakeStructureMapRegistry.cs
public class FakeStructureMapRegistry : Registry
{
public FakeStructureMapRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
});
For<IDataManager>()
.Singleton()
.Use(A.Fake<DataManager>(
y => y.WithArgumentsForConstructor(() =>
new DataManager("Fake Connection String"))));
For<ISmtpService>()
.Singleton()
.Use(A.Fake<SmtpService>(
y => y.WithArgumentsForConstructor(() =>
new SmtpService("Fake Smtp Server"))));
For<ILoggingService>().Use(A.Fake<ILoggingService>());
For<IPaymentService>().Use(A.Fake<IPaymentService>());
For<IWarehouseService>().Use(A.Fake<IWarehouseService>());
For<IEmailService>().Use(A.Fake<IEmailService>());
For<IOrderingService>().Use(A.Fake<IOrderingService>());
}
}
Question: Why I am also registering SUT OrderingService inside FakeStructureMapRegistry? Because in this Fake registry I don’t want to keep changing which class is under test and which one isn’t. I will register all the Fakes at once and not worry about it. More on that in the next paragraph. Now we will use this Fake registry in our setup method. So here is how my modified unit test looks like.
[TestClass]
public class OrderingServiceTest
{
private IPaymentService _paymentService;
private IEmailService _emailService;
private ILoggingService _loggingService;
private IWarehouseService _warehouseService;
private IContainer _container;
//SUT
private IOrderingService _orderingService;
[TestInitialize]
public void Setup()
{
_container = new Container(x => x.AddRegistry(new FakeStructureMapRegistry())); //registering all FAKES.
_container.EjectAllInstancesOf<IOrderingService>(); //ejecting FAKE SUT
_container.Configure(x => x.AddType(typeof(IOrderingService), typeof(OrderingService))); //registering SUT
_paymentService = _container.GetInstance<IPaymentService>();
_emailService = _container.GetInstance<IEmailService>();
_loggingService = _container.GetInstance<ILoggingService>();
_warehouseService = _container.GetInstance<IWarehouseService>();
_orderingService = _container.GetInstance<IOrderingService>();
}
[TestMethod]
public void PlaceOrder_OnPassingNonNullOrder_ShouldPlaceOrderSuccessfully()
{
//Arrange
var fixture = new Fixture();
var order = fixture.Create<Order>();
//Act
var result = _orderingService.PlaceOrder(order);
//Assert
Assert.IsTrue(result);
}
}
Let’s go into detail about the first three lines in the Setup method and rest is self explanatory. Line 1 – we create a new structuremap container and add our FakeStructureMapRegistry class. That takes care of configuring all the Fake dependencies including our SUT which should never be a mocked or faked object. Hence line 2 – which tell the container to eject all the instances of IOrderingService because I will configure it as not Fake and real OrderingService on Line 3. So that’s all. The rest of the lines gets instances from the container which will look a bit familiar to the constructor of OrderingService. I love this setup method. This exactly feels how I would write my system under test. If you were to configure another service say LoggingService then it would be like this.
[TestClass]
public class LoggingServiceTest
{
private IContainer _container;
private IDataManager _dataManager;
private ILoggingService _loggingService;
[TestInitialize]
public void Setup()
{
_container = new Container(x => x.AddRegistry(new FakeStructureMapRegistry())); //registering all FAKES.
_container.EjectAllInstancesOf<ILoggingService>(); //ejecting FAKE SUT
_container.Configure(x => x.AddType(typeof(ILoggingService), typeof(LoggingService))); //registering SUT
_dataManager = _container.GetInstance<IDataManager>();
_loggingService = _container.GetInstance<ILoggingService>();
}
}
So that’s it. What do you guys think of this way of setting up? Please let me know in the comments sections below and thank you for reading the post.
ReplyDeleteWow,nice information you posted here. Keep up the excellent work.
Manual Testing Training in Chennai
testing courses in chennai
Manual Testing Training in T Nagar
Mobile Testing Training in Chennai
Mobile Testing Training
Drupal Training in Chennai
Photoshop Classes in Chennai
Manual Testing Training in Chennai