unit testing & constructor dependency injection

21,478

Solution 1

As you did not show any code I'm guessing 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 that simple enough?


Update

Don't abuse a DI container as a ServiceLocator. Your idea to inject the payment creator as ctor parameter is the right way to go. 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);
}

Solution 2

You shouldn't be using your DI container(Ninject) during such unit-tests. You would manually inject the mock object when newing up the class under test. Then verify the call was made on the mock.

Share:
21,478
Karl Cassar
Author by

Karl Cassar

A passionate coder &amp; web developer based in Malta (Europe) who constantly tries to keep abreast of the latest technologies and finding new ways to increase code quality and robustness. Focusing mainly on .Net technologies / C#. Lately became extremely keen on TDD, BDD and similar practices/methodologies to constantly improve on software quality, reliability and coding efficiency.

Updated on October 17, 2020

Comments

  • Karl Cassar
    Karl Cassar over 3 years

    I have an issue regarding how one would go about designing an application suited for unit-testing.

    I am trying to implement the SRP (Single-Responsibility Principle), and from what I understood this involves splitting out most functionality in seperate, dedicated classes to keep code more organised. For example, I have this specific scenario.

    A class RecurringProfile, which has a method .ActivateProfile(). What this method does is mark the status as activated, and create the next (first) recurring payment for the next due date. I was going to split out the functionality to create the next recurring payment in a seperate class, for example RecurringProfileNextPaymentCreator. My instant idea is to have this class take the 'RecurringProfile' as a parameter in it's constructor:

    RecurringProfileNextPaymentCreator(IRecurringProfile profile)
    

    However, I think this would be problematic for unit-testing. I would like to create a unit-test which tests out the ActivateProfile functionality. This method would get an instance of an IRecurringProfileNextPaymentCreator via dependency injection (Ninject), and call the method .CreateNextPayment.

    My idea to create a unit-test was to create a mock of an IRecurringProfileNextPaymentCreator, and substitue that so that I can verify that the .ActivateProfile() actually called the method. However, due to the constructor parameter, this would not fit as a default constructor for NInject. Having to create a custom NInject provider just for such a case (where I can have many such classes all over the solution) would be a bit overkill.

    Any ideas / best practices how one would go about this?

    -- Below is a sample code regarding the above example: (Please note that the code is hand-written, and is not syntactically 100% correct)

    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 a sample unit-test:

    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());
    
    }
    

    Going up to the sample code, my issue is whether it is a good practice to pass over the RecurringProfile as a parameter to the creator.CreateNextPayment() method, or whether it makes more sense to somehow pass the RecurringProfile to the DI-framework, when getting an instance of an IRecurringProfileNextPaymentCreator, considering that the IRecurringProfileNextPaymentCreator will always act on an IRecurringProfile to create the next payment. Hope this makes the question a bit more clear.