Reputation: 3278
I am using Autofac
for IoC
Here is my container initiator class, which the responsibility is to register the dependencies.
public class ContainerInit
{
public static IContainer BuildContainer()
{
var conFac = new ContainerFactory();
var builder = new ContainerBuilder();
builder.Register(conFac).As<IContainerFactory>().SingleInstance();
builder.Register(c=> new MainClass(conFac)).As<IMainClass>().SingleInstance();
builder.Register(c=> new Database(conFac)).As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
ContainerFactory.SetContainer(container);
return container;
}
}
Problem with this approach is, I need to pass IContainerFactory
to the constructor of every class I use in my application as follow
public class MainClass: IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(IContainerFactory containerFactory)
{
_logger = containerFactory.GetInstance<ILogger>();
_db = containerFactory.GetInstance<IDatabase>(); //example
}
public AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
So it is difficult to unit test these classes.
How can come up with a good solution?
Upvotes: 8
Views: 37036
Reputation: 247018
Your current Service Locator Anti-Pattern is what makes your code difficult to test in isolation as well as makes the class misleading about what it actually depends on.
MainClass
should be refactored to follow Explicit Dependencies Principle
public class MainClass : IMainClass
private readonly ILogger logger;
private readonly IDatabase db;
public MainClass(ILogger logger, IDatabase db) {
this.logger = logger;
this.db = db;
}
public void AddDetails(Data data) {
//do some business operations
db.Add(data);
logger.Information("added");
}
}
The same pattern should also be followed for any other class you have that depends on the container factory, like Database
.
You would however need to also refactor the container registration accordingly
public class ContainerInit {
public static IContainer BuildContainer() {
var builder = new ContainerBuilder();
builder.RegisterType<MainClass>().As<IMainClass>().SingleInstance();
builder.RegisterType<Database>().As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
return container;
}
}
Testing MainClass
would required you to mock only the necessary dependencies of the class under test.
[TestClass]
public class MainClassTests {
[TestMethod]
public void Should_AddDetails_To_Database() {
// Arrange
var mockDb = new Mock<IDatabase>();
var data = new Data();
var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);
// Act
mainClass.AddDetails(data);
// Assert
mockDb.Verify(_ => _.Add(data), Times.Once);
}
}
Upvotes: 4
Reputation: 3293
A better approach would be to pass the dependencies you need in your class into your constructor:
public class MainClass : IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(ILogger logger, IDatabase db)
{
_logger = logger;
_db = db;
}
public void AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
Then you could use a mocking framework such as Moq to mock your class dependencies and perform verifications on whether the dependencies were called:
[TestClass]
public class UnitTest1
{
private Mock<ILogger> _mockLogger = new Mock<ILogger>();
private Mock<IDatabase> _mockDb = new Mock<IDatabase>();
[TestMethod]
public void TestMethod1()
{
// arrange
var mainClass = new MainClass(_mockLogger.Object, _mockDb.Object);
var data = new Data();
// act
mainClass.AddDetails(data);
// assert
_mockDb
.Verify(v => v.Add(data), Times.Once);
}
}
I would not verify your log message though as this could change and make the test brittle. Only verify functionality which is essential to doing what the method is intended for.
Upvotes: 11
Reputation: 16049
Here I would like to share solution, which I use in my project
To do unit testing of particular function, I use below structure
[TestClass]
public class TestSomeFunction
{
public IComponentContext ComponentContext { get; set; }
[TestInitialize]
public void Initialize()
{
//Registering all dependencies required for unit testing
this.ComponentContext = builder.Build(); //You have not build your container in your question
}
[TestMethod]
public void Testfunction()
{
//Resolve perticular dependency
var _logger = containerFactory.Resolve<ILogger>();
//Test my function
//use _logger
}
}
Upvotes: 1