Unit Testing and Constructor Dependency Injection

I have a problem regarding how you could develop an application suitable for unit testing.

I am trying to implement SRP (the principle of single responsibility), and from what I understand, this includes the separation of most functions in separate dedicated classes, so that the code is more organized. For example, I have this specific scenario.

The RecurringProfile class that has the .ActivateProfile() method. What this method does is mark the status as activated and create the next (first) recurring payment for the next date. I was going to split the functionality to create the next recurring payment in a separate class, for example RecurringProfileNextPaymentCreator . My idea is for this class to accept 'RecurringProfile' as a parameter in its constructor:

 RecurringProfileNextPaymentCreator(IRecurringProfile profile) 

However, I think this would be problematic for unit testing. I would like to create a unit test that tests the functionality of ActivateProfile . This method will receive an instance of IRecurringProfileNextPaymentCreator through dependency injection (Ninject) and will call the .CreateNextPayment method.

My idea of โ€‹โ€‹creating a unit test was to create an IRecurringProfileNextPaymentCreator layout and replace it so that I can verify that the .ActivateProfile() method is actually called a method. However, due to the constructor parameter, this will not match the default constructor for NInject. The need to create a custom NInject provider only for such a case (where I can have many such classes throughout the solution) will be a bit overkill.

Any ideas / best practices how this could be done?

- The following is an example of code related to the above example: (Please note that the code is written by hand and is not 100% syntactical)

 public class RecurringProfile { public void ActivateProfile() { this.Status = Enums.ProfileStatus.Activated; //now it should create the first recurring payment IRecurringProfileNextPaymentCreator creator = NInject.Get<IRecurringProfileNextPaymentCreator>(); creator.CreateNextPayment(this); //this is what I'm having an issue about } } 

And the unit-test example:

 public void TestActivateProfile() { var mockPaymentCreator = new Mock<IRecurringProfileNextPaymentCreator>(); NInject.Bind<IRecurringProfileNextPaymentCreator>(mockPaymentCreator.Object); RecurringProfile profile = new RecurringProfile(); profile.ActivateProfile(); Assert.That(profile.Status == Enums.ProfileStatus.Activated); mockPayment.Verify(x => x.CreateNextPayment(), Times.Once()); } 

Approaching the sample code, my problem is whether to pass through RecurringProfile as a parameter to the creator.CreateNextPayment() method or does it make sense to somehow pass the RecurringProfile to a DI -framework when receiving an instance of IRecurringProfileNextPaymentCreator , given that IRecurringProfileNextPaymentCreator will always act on IRecurringProfile to create the next payment. I hope this issue becomes more clear.

+6
source share
2 answers

Since you did not show the code, I assume that you want to do something like this

 public class RecurringProfile { private readonly DateTime _dueDate; private readonly TimeSpan _interval; public RecurringProfile(DateTime dueDate, TimeSpan interval) { _dueDate = dueDate; _interval = interval; } public bool IsActive { get; private set; } public DateTime DueDate { get { return _dueDate; } } public TimeSpan Interval { get { return _interval; } } public RecurringProfile ActivateProfile() { this.IsActive = true; return new RecurringProfile(this.DueDate + this.Interval, this.Interval); } } 

Isnโ€™t it so simple?


Update

Do not abuse the DI container as a ServiceLocator. Your idea of โ€‹โ€‹introducing a payment creator as a ctor parameter is the right way. ServiceLocator is considered an anti-pattern in modern application architecture . Something like the code below should work fine.

 [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { var mock = new Mock<INextPaymentCreator>(); DateTime dt = DateTime.Now; var current = new RecurringProfile(mock.Object, dt, TimeSpan.FromDays(30)); current.ActivateProfile(); mock.Verify(c => c.CreateNextPayment(current), Times.Once()); } } public class RecurringProfile { private readonly INextPaymentCreator _creator; private readonly DateTime _dueDate; private readonly TimeSpan _interval; public RecurringProfile(INextPaymentCreator creator, DateTime dueDate, TimeSpan interval) { _creator = creator; _dueDate = dueDate; _interval = interval; } public bool IsActive { get; private set; } public DateTime DueDate { get { return _dueDate; } } public TimeSpan Interval { get { return _interval; } } public RecurringProfile ActivateProfile() { this.IsActive = true; var next = this._creator.CreateNextPayment(this); return next; } } public interface INextPaymentCreator { RecurringProfile CreateNextPayment(RecurringProfile current); } 
+3
source

You should not use your DI container (Ninject) during such unit tests. You must manually enter the object layout when updating the test class. Then check if the call has been made to the layout.

+5
source

Source: https://habr.com/ru/post/927746/


All Articles