Convert From Tightly to Loosely Coupled Dependencies

Introduction

Tightly coupled dependencies are a challenge, especially in unit testing. This is because switching between real dependencies (for production) and mock dependencies (for testing) in tightly coupled code is complicated. Therefore, to test tightly coupled code, we first need to transform it into loosely coupled code.

We can use a dependency injection (DI) pattern to transform tightly coupled dependencies into loosely coupled dependencies. This can be done using two approaches:

  • Using a DI container
  • Injecting dependencies manually

When using the DI pattern, the recommended best practice is to use a DI container. This is because this container easily defines all dependencies and manages them for you. To understand the theory behind dependency injection, it is essential to understand injecting dependencies manually and this is the objective of this lesson.

Switching from tightly coupled to loosely coupled code

The code below demonstrates how to switch from tightly coupled to loosely coupled dependencies. Suppose we have a BankAccount class that includes an Amount field. The Withdraw and Deposit methods allow for withdrawing and depositing funds to and from the bank account. This class is designed only to enable withdrawals on weekdays. To achieve this business rule, the DayChecker class is used, which returns whether or not the transaction date is on a weekday. The bank account is initialized with a balance of 1000.

Tightly coupled code

When you run the application you’ll be prompted to withdraw a specified amount. Enter any value between 0 and 1000 and observe how the resulting balance is affected. If you run the application on a weekday, the resulting balance will decrease by the amount specified. If you run the application on the weekend, the resulting balance will not be reduced by the specified amount. Note that the server time is set to EST (GMT - 5 hours).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Project
{
public class BankAccount
{
   public decimal Amount { get; set; }
   public void Withdraw(decimal withdrawAmount)
   {
       DayChecker dayChecker = new DayChecker();
       if (Amount > 0 && !dayChecker.IsWeekend())
       {
           Amount -= withdrawAmount;
       }
       return;
   }

   public void Deposit(decimal depositAmount)
   {
       Amount += depositAmount;
       return;
   }
}
}
Tightly coupled code

What if we wanted to test the BankAccount class on both weekends and weekdays? The code above cannot accomplish this because the BankAccount class is tightly coupled to DayChecker, which only gets the present day. We cannot artificially set the present day to some fixed weekday or weekend date, for the purposes of testing. To solve this problem, we need to first change the design from a tightly coupled to a loosely coupled design.

Loosely coupled code

The solution is to use the IDayChecker interface instead of DayChecker as a dependency of BankAccount. The next step would be to create a DayChecker implementation of IDayChecker.

The code below works the same as above, except that it is loosely coupled using the IDayChecker interface described above. The code changes and additions in each file are highlighted. Run the code below and observe how its behavior remains unchanged from the first example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Project
{
    public class BankAccount
    {
        private readonly IDayChecker _dayChecker;

        public decimal Amount { get; set; }

        public BankAccount(IDayChecker dayChecker)
        {
            _dayChecker = dayChecker;
        }

        public void Withdraw(decimal withdrawAmount)
        {
            if (Amount > 0 && !_dayChecker.IsWeekend())
            {
                Amount -= withdrawAmount;
            }
            return;
        }

        public void Deposit(decimal depositAmount)
        {
            Amount += depositAmount;
            return;
        }

    }
}
Loosely coupled code

Dependency injection

The above conversion from tightly to loosely coupled dependencies utilized a software pattern called dependency injection. Dependency injection is a technique in which an object receives other objects that it depends on. These objects are called dependencies. In the example above, instead of creating an instance of the dependency—the DayChecker class—in the implementation of the Withdraw method of the BankAccount class, we passed the dependency as a parameter to the constructor of the BankAccount class. The code is designed so that another IDayChecker implementation may be used without changing the BankAccount class.

The dependency injection was performed manually in the example above. Here, we did not use a dependency injection container. Using a dependency injection container can make managing dependencies more manageable and is considered best practice.

Adding tests to loosely coupled dependencies

Now that the BankAccount class is loosely coupled with the IDayChecker class, we can add unit tests that use a fake DayChecker class called DayCheckerTestImplementation. An instance of DayCheckerTestImplementation is a mock object constructed with a provided date and used specifically for the purposes of testing.

The code in the DayCheckerTest class in the SPA below tests the BankAccount class using a fake DayCheckerTestImplementation object. A weekend date is provided in a test (line 15) to test the weekend’s withdrawal rules and a weekday date is provided in a test (line 29) to test the weekday’s withdrawal rules. Observe how both tests pass and that the withdrawal rules work equally well for weekdays and weekends.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Project
{
    public class BankAccount
    {
        private readonly IDayChecker _dayChecker;

        public decimal Amount { get; set; }

        public BankAccount(IDayChecker dayChecker)
        {
            _dayChecker = dayChecker;
        }

        public void Withdraw(decimal withdrawAmount)
        {
            if (Amount > 0 && !_dayChecker.IsWeekend())
            {
                Amount -= withdrawAmount;
            }
            return;
        }

        public void Deposit(decimal depositAmount)
        {
            Amount += depositAmount;
            return;
        }

    }
}
Loosely coupled code with testing

Conclusion

We can use the dependency injection pattern to transform tightly coupled dependencies into loosely coupled dependencies. Once the application follows a loosely coupled design, unit testing becomes considerably easier. The application code remains unchanged whether we supply it with a real dependency object for production or a fake dependency object for testing.